@hyperscale0/hsx 2.1.1 → 2.2.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.
- package/CHANGELOG.md +28 -7
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +39 -5
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +0 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +15 -1
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +3 -3
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +22 -12
- package/dist/src/cost.js.map +1 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +10 -14
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +20 -84
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/typecheck.d.ts.map +1 -1
- package/dist/src/typecheck.js +171 -39
- package/dist/src/typecheck.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +4 -2
- package/docs/guide/01-first-program.md +1 -1
- package/docs/guide/06-schedules.md +5 -1
- package/docs/guide/08-writing-a-module.md +48 -0
- package/docs/llms-full.txt +1928 -421
- package/docs/llms.txt +2 -3
- package/docs/reference/cli.md +6 -5
- package/docs/reference/diagnostics.md +9 -9
- package/docs/reference/grammar.md +2 -3
- package/docs/reference/std/advance.md +94 -17
- package/docs/reference/std/cancellable_booking.md +138 -17
- package/docs/reference/std/captured_payment.md +96 -24
- package/docs/reference/std/conditional_disbursement.md +82 -14
- package/docs/reference/std/credit_facility.md +89 -16
- package/docs/reference/std/held_payment.md +155 -55
- package/docs/reference/std/instant_transfer.md +79 -12
- package/docs/reference/std/metered.md +71 -9
- package/docs/reference/std/pooled_split.md +76 -7
- package/docs/reference/std/premium_forward.md +100 -20
- package/docs/reference/std/reconciled_payout.md +84 -14
- package/docs/reference/std/rotating_pool.md +112 -24
- package/docs/reference/std/scheduled.md +117 -32
- package/docs/reference/std/security_deposit.md +119 -27
- package/docs/reference/std/settlement_batch.md +105 -24
- package/docs/reference/std/swap.md +113 -26
- package/docs/reference/std/threshold_pool.md +120 -29
- package/docs/reference/std/weighted_distribution.md +117 -21
- package/docs/reference/types.md +15 -15
- package/docs/reference/udl-output.md +6 -6
- package/examples/01-first-program/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/README.md +1 -1
- package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/photo-booth.hsx +1 -1
- package/examples/04-complete-product/README.md +1 -1
- package/examples/05-authored-instrument/README.md +5 -0
- package/examples/05-authored-instrument/payment.hsx +37 -0
- package/examples/05-watch-club/watch-club.hsx +0 -1
- package/examples/README.md +1 -2
- package/examples/advance/advance.udl +29 -6
- package/examples/cancellable_booking/cancellable_booking.udl +19 -0
- package/examples/captured_payment/captured_payment.hsx +0 -4
- package/examples/captured_payment/captured_payment.udl +6 -0
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -2
- package/examples/conditional_disbursement/conditional_disbursement.udl +4 -0
- package/examples/cost-table.json +136 -8
- package/examples/credit_facility/credit_facility.hsx +0 -3
- package/examples/credit_facility/credit_facility.udl +17 -1
- package/examples/held_payment/held_payment.udl +16 -0
- package/examples/instant_transfer/instant_transfer.udl +6 -0
- package/examples/metered/metered.udl +5 -1
- package/examples/pooled_split/pooled_split.udl +4 -0
- package/examples/premium_forward/premium_forward.udl +4 -0
- package/examples/reconciled_payout/reconciled_payout.udl +7 -0
- package/examples/rotating_pool/rotating_pool.udl +8 -0
- package/examples/scheduled/scheduled.udl +18 -3
- package/examples/security_deposit/security_deposit.udl +13 -0
- package/examples/settlement_batch/settlement_batch.udl +4 -0
- package/examples/swap/swap.udl +10 -0
- package/examples/threshold_pool/threshold_pool.udl +11 -0
- package/examples/weighted_distribution/weighted_distribution.udl +9 -0
- package/package.json +10 -10
- package/skills/hsx/SKILL.md +2 -36
- package/src/cli.ts +41 -5
- package/src/compile.ts +14 -6
- package/src/cost.ts +13 -16
- package/src/parse.ts +15 -10
- package/src/std-bundle.ts +21 -88
- package/src/typecheck.ts +207 -34
- package/src/version.ts +1 -1
- package/std/SEMANTICS.md +33 -128
- package/std/money_flows/advance.hsx +39 -26
- package/std/money_flows/cancellable_booking.hsx +275 -8
- package/std/money_flows/captured_payment.hsx +1 -9
- package/std/money_flows/conditional_disbursement.hsx +0 -6
- package/std/money_flows/credit_facility.hsx +0 -9
- package/std/money_flows/held_payment.hsx +39 -1
- package/std/money_flows/index.hsx +2 -1
- package/std/money_flows/metered.hsx +1 -5
- package/std/money_flows/scheduled.hsx +29 -119
- package/std/money_flows/threshold_pool.hsx +40 -5
- package/std/money_flows/weighted_distribution.hsx +47 -5
- package/docs/reference/std/recurring_collection.md +0 -25
- package/examples/recurring_collection/README.md +0 -3
- package/examples/recurring_collection/recurring_collection.hsx +0 -21
- package/examples/recurring_collection/recurring_collection.udl +0 -1135
- package/std/money_flows/recurring_collection.hsx +0 -72
package/src/typecheck.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
fixedIsoDurationMs,
|
|
3
|
+
referencedUdlInstrumentIds,
|
|
2
4
|
deriveUdlActionEffects,
|
|
3
5
|
resolveUdlActionPlans,
|
|
4
6
|
udlDocumentSchema,
|
|
@@ -453,6 +455,28 @@ export function checkGeneralProgram(
|
|
|
453
455
|
);
|
|
454
456
|
}
|
|
455
457
|
|
|
458
|
+
const prefixByInstrument = new Map<string, string>();
|
|
459
|
+
if (options.publishedCatalog) {
|
|
460
|
+
for (const inst of options.publishedCatalog.instruments) {
|
|
461
|
+
if (inst.idPrefix) {
|
|
462
|
+
prefixByInstrument.set(inst.id, inst.idPrefix);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
for (const [name, decl] of templates) {
|
|
467
|
+
const prefix =
|
|
468
|
+
stringSlot(decl.body, "idPrefix", "id_prefix") ?? prefixFor(name);
|
|
469
|
+
prefixByInstrument.set(name, prefix);
|
|
470
|
+
}
|
|
471
|
+
for (const candidate of allocated) {
|
|
472
|
+
const prefix =
|
|
473
|
+
stringSlot(candidate.body, "idPrefix", "id_prefix") ??
|
|
474
|
+
prefixFor(candidate.name.name);
|
|
475
|
+
prefixByInstrument.set(candidate.name.name, prefix);
|
|
476
|
+
}
|
|
477
|
+
const resolvePrefix = (target: string): string | undefined =>
|
|
478
|
+
prefixByInstrument.get(target);
|
|
479
|
+
|
|
456
480
|
const instruments: TypedInstrument[] = [];
|
|
457
481
|
const ids = new Set<string>();
|
|
458
482
|
for (const candidate of allocated) {
|
|
@@ -483,6 +507,7 @@ export function checkGeneralProgram(
|
|
|
483
507
|
candidate.ports,
|
|
484
508
|
candidate.aliases,
|
|
485
509
|
diagnostics,
|
|
510
|
+
resolvePrefix,
|
|
486
511
|
);
|
|
487
512
|
if (checked) instruments.push(checked);
|
|
488
513
|
}
|
|
@@ -499,7 +524,11 @@ export function checkGeneralProgram(
|
|
|
499
524
|
);
|
|
500
525
|
}
|
|
501
526
|
|
|
502
|
-
crossInstrumentReferenceDiagnostics(
|
|
527
|
+
crossInstrumentReferenceDiagnostics(
|
|
528
|
+
instruments,
|
|
529
|
+
diagnostics,
|
|
530
|
+
new Set(templates.keys()),
|
|
531
|
+
);
|
|
503
532
|
|
|
504
533
|
if (
|
|
505
534
|
header &&
|
|
@@ -656,6 +685,15 @@ function checkPublishedProgram(
|
|
|
656
685
|
);
|
|
657
686
|
continue;
|
|
658
687
|
}
|
|
688
|
+
if (action?.engineOwned || authoredAction?.slots.engineOwned === true) {
|
|
689
|
+
report(
|
|
690
|
+
"HSX1021",
|
|
691
|
+
exposure.span,
|
|
692
|
+
`${target} is engine-owned`,
|
|
693
|
+
"expose a caller action instead",
|
|
694
|
+
);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
659
697
|
if (!CAMEL_CASE.test(exposure.publicName.name)) {
|
|
660
698
|
report(
|
|
661
699
|
"HSX1020",
|
|
@@ -843,24 +881,7 @@ function publishedInstrumentDependencies(
|
|
|
843
881
|
instrument: UdlInstrument,
|
|
844
882
|
idByPrefix: ReadonlyMap<string, string>,
|
|
845
883
|
): readonly string[] {
|
|
846
|
-
const found = new Set
|
|
847
|
-
const visit = (value: unknown): void => {
|
|
848
|
-
if (Array.isArray(value)) {
|
|
849
|
-
value.forEach(visit);
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (!value || typeof value !== "object") return;
|
|
853
|
-
for (const [key, child] of Object.entries(value)) {
|
|
854
|
-
if (
|
|
855
|
-
(key === "instrumentId" || key === "childInstrumentId") &&
|
|
856
|
-
typeof child === "string"
|
|
857
|
-
) {
|
|
858
|
-
found.add(child);
|
|
859
|
-
}
|
|
860
|
-
visit(child);
|
|
861
|
-
}
|
|
862
|
-
};
|
|
863
|
-
visit(instrument);
|
|
884
|
+
const found = new Set(referencedUdlInstrumentIds(instrument));
|
|
864
885
|
for (const field of Object.values(instrument.fields)) {
|
|
865
886
|
const prefix =
|
|
866
887
|
typeof field.pattern === "string"
|
|
@@ -1110,7 +1131,6 @@ const applicationMetadataKeys = new Set([
|
|
|
1110
1131
|
"action",
|
|
1111
1132
|
"agent_description",
|
|
1112
1133
|
"description",
|
|
1113
|
-
"journeys",
|
|
1114
1134
|
"nav",
|
|
1115
1135
|
"navigation",
|
|
1116
1136
|
"summary",
|
|
@@ -1423,7 +1443,12 @@ function inferSettlementTypeArgument(
|
|
|
1423
1443
|
typeParameter: string,
|
|
1424
1444
|
): Expr | undefined {
|
|
1425
1445
|
for (const parameter of template.parameters) {
|
|
1426
|
-
const
|
|
1446
|
+
const declaredType = parameter.type;
|
|
1447
|
+
const parameterType =
|
|
1448
|
+
declaredType.kind === "type_apply" &&
|
|
1449
|
+
declaredType.callee.name === "optional"
|
|
1450
|
+
? (declaredType.args[0] ?? declaredType)
|
|
1451
|
+
: declaredType;
|
|
1427
1452
|
if (
|
|
1428
1453
|
parameterType.kind !== "type_apply" ||
|
|
1429
1454
|
parameterType.callee.name !== "money" ||
|
|
@@ -1727,9 +1752,53 @@ function instantiate(
|
|
|
1727
1752
|
values,
|
|
1728
1753
|
portDependencies,
|
|
1729
1754
|
);
|
|
1755
|
+
const binding: Entry = {
|
|
1756
|
+
key: { kind: "ident", name: "template_binding", span: application.span },
|
|
1757
|
+
qualifiers: [],
|
|
1758
|
+
span: application.span,
|
|
1759
|
+
value: {
|
|
1760
|
+
kind: "block",
|
|
1761
|
+
span: application.span,
|
|
1762
|
+
entries: [
|
|
1763
|
+
{
|
|
1764
|
+
key: { kind: "ident", name: "id", span: application.span },
|
|
1765
|
+
qualifiers: [],
|
|
1766
|
+
span: application.span,
|
|
1767
|
+
value: {
|
|
1768
|
+
kind: "string",
|
|
1769
|
+
value: template.name.name,
|
|
1770
|
+
span: application.span,
|
|
1771
|
+
},
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
key: { kind: "ident", name: "parameters", span: application.span },
|
|
1775
|
+
qualifiers: [],
|
|
1776
|
+
span: application.span,
|
|
1777
|
+
value: {
|
|
1778
|
+
kind: "block",
|
|
1779
|
+
span: application.span,
|
|
1780
|
+
entries: resolvedArgs.flatMap(({ parameter }) => {
|
|
1781
|
+
const value = values.get(parameter.name.name);
|
|
1782
|
+
return value &&
|
|
1783
|
+
["string", "number", "boolean"].includes(value.kind)
|
|
1784
|
+
? [
|
|
1785
|
+
{
|
|
1786
|
+
key: parameter.name,
|
|
1787
|
+
qualifiers: [],
|
|
1788
|
+
span: parameter.span,
|
|
1789
|
+
value,
|
|
1790
|
+
},
|
|
1791
|
+
]
|
|
1792
|
+
: [];
|
|
1793
|
+
}),
|
|
1794
|
+
},
|
|
1795
|
+
},
|
|
1796
|
+
],
|
|
1797
|
+
},
|
|
1798
|
+
};
|
|
1730
1799
|
return {
|
|
1731
1800
|
...body,
|
|
1732
|
-
entries: body.entries.map((row) => {
|
|
1801
|
+
entries: [...body.entries, binding].map((row) => {
|
|
1733
1802
|
if (row.key.name !== "fields" || row.value.kind !== "block") return row;
|
|
1734
1803
|
return {
|
|
1735
1804
|
...row,
|
|
@@ -2305,9 +2374,16 @@ function substituteExpr(expr: Expr, values: ReadonlyMap<string, Expr>): Expr {
|
|
|
2305
2374
|
}
|
|
2306
2375
|
: {}),
|
|
2307
2376
|
key: substituteName(entry.key, nestedValues),
|
|
2308
|
-
qualifiers: entry.qualifiers.
|
|
2377
|
+
qualifiers: entry.qualifiers.flatMap((qualifier, index) => {
|
|
2309
2378
|
const value = nestedValues.get(qualifier.name);
|
|
2310
2379
|
const fixed = fixedQualifier(entry.key.name, qualifier.name, index);
|
|
2380
|
+
if (
|
|
2381
|
+
entry.key.name === "on" &&
|
|
2382
|
+
!fixed &&
|
|
2383
|
+
index > 0 &&
|
|
2384
|
+
value?.kind === "list"
|
|
2385
|
+
)
|
|
2386
|
+
return value.items.map((item) => nameFromValue(qualifier, item));
|
|
2311
2387
|
return !fixed && value
|
|
2312
2388
|
? nameFromValue(qualifier, value)
|
|
2313
2389
|
: substituteName(qualifier, nestedValues);
|
|
@@ -2804,6 +2880,17 @@ function evaluateCompileTimeCall(
|
|
|
2804
2880
|
: undefined;
|
|
2805
2881
|
case "basis_points":
|
|
2806
2882
|
return first?.kind === "percent" ? numeric(first.bps) : undefined;
|
|
2883
|
+
case "shift_date": {
|
|
2884
|
+
const instant = first ? Date.parse(nameText(first)) : NaN;
|
|
2885
|
+
const offset = second ? fixedIsoDurationMs(nameText(second)) : null;
|
|
2886
|
+
if (!Number.isFinite(instant) || offset === null)
|
|
2887
|
+
return text(NONE_SENTINEL);
|
|
2888
|
+
const direction = third && nameText(third) === "before" ? -1 : 1;
|
|
2889
|
+
const shifted = new Date(instant + direction * offset);
|
|
2890
|
+
return Number.isFinite(shifted.getTime())
|
|
2891
|
+
? text(shifted.toISOString())
|
|
2892
|
+
: text(NONE_SENTINEL);
|
|
2893
|
+
}
|
|
2807
2894
|
case "names": {
|
|
2808
2895
|
const count = number(second);
|
|
2809
2896
|
if (!first || count === undefined || count < 0 || !third)
|
|
@@ -2826,6 +2913,15 @@ function evaluateCompileTimeCall(
|
|
|
2826
2913
|
span: expr.span,
|
|
2827
2914
|
}
|
|
2828
2915
|
: undefined;
|
|
2916
|
+
case "without":
|
|
2917
|
+
return first?.kind === "list" && second
|
|
2918
|
+
? {
|
|
2919
|
+
...first,
|
|
2920
|
+
items: first.items.filter(
|
|
2921
|
+
(item) => nameText(item) !== nameText(second),
|
|
2922
|
+
),
|
|
2923
|
+
}
|
|
2924
|
+
: undefined;
|
|
2829
2925
|
case "concat_lists":
|
|
2830
2926
|
return expr.args.every((arg) => arg.kind === "list")
|
|
2831
2927
|
? {
|
|
@@ -2992,9 +3088,16 @@ function expandComprehensions(
|
|
|
2992
3088
|
const substituted: Entry = {
|
|
2993
3089
|
...row,
|
|
2994
3090
|
key: substituteName(row.key, locals),
|
|
2995
|
-
qualifiers: row.qualifiers.
|
|
3091
|
+
qualifiers: row.qualifiers.flatMap((qualifier, index) => {
|
|
2996
3092
|
const value = locals.get(qualifier.name);
|
|
2997
3093
|
const fixed = fixedQualifier(row.key.name, qualifier.name, index);
|
|
3094
|
+
if (
|
|
3095
|
+
row.key.name === "on" &&
|
|
3096
|
+
!fixed &&
|
|
3097
|
+
index > 0 &&
|
|
3098
|
+
value?.kind === "list"
|
|
3099
|
+
)
|
|
3100
|
+
return value.items.map((item) => nameFromValue(qualifier, item));
|
|
2998
3101
|
return !fixed && value
|
|
2999
3102
|
? nameFromValue(qualifier, value)
|
|
3000
3103
|
: substituteName(qualifier, locals);
|
|
@@ -3077,6 +3180,7 @@ function checkInstrument(
|
|
|
3077
3180
|
ports: ReadonlyMap<string, readonly PortDecl[]>,
|
|
3078
3181
|
aliases: ReadonlyMap<string, Expr>,
|
|
3079
3182
|
diagnostics: GeneralDiagnostic[],
|
|
3183
|
+
resolvePrefix?: (target: string) => string | undefined,
|
|
3080
3184
|
): TypedInstrument | undefined {
|
|
3081
3185
|
if (!SNAKE_CASE.test(name.name)) {
|
|
3082
3186
|
diagnostics.push({
|
|
@@ -3090,7 +3194,7 @@ function checkInstrument(
|
|
|
3090
3194
|
const fieldsEntry = entry(body, "fields");
|
|
3091
3195
|
const fields =
|
|
3092
3196
|
fieldsEntry?.value.kind === "block"
|
|
3093
|
-
? checkFields(fieldsEntry.value, aliases, diagnostics)
|
|
3197
|
+
? checkFields(fieldsEntry.value, aliases, diagnostics, resolvePrefix)
|
|
3094
3198
|
: [];
|
|
3095
3199
|
if (!fieldsEntry) {
|
|
3096
3200
|
diagnostics.push({
|
|
@@ -3597,6 +3701,7 @@ function checkFields(
|
|
|
3597
3701
|
block: BlockExpr,
|
|
3598
3702
|
aliases: ReadonlyMap<string, Expr>,
|
|
3599
3703
|
diagnostics: GeneralDiagnostic[],
|
|
3704
|
+
resolvePrefix?: (target: string) => string | undefined,
|
|
3600
3705
|
): TypedField[] {
|
|
3601
3706
|
const result: TypedField[] = [];
|
|
3602
3707
|
const names = new Set<string>();
|
|
@@ -3622,7 +3727,7 @@ function checkFields(
|
|
|
3622
3727
|
continue;
|
|
3623
3728
|
}
|
|
3624
3729
|
names.add(name);
|
|
3625
|
-
result.push(lowerField(row, aliases, diagnostics));
|
|
3730
|
+
result.push(lowerField(row, aliases, diagnostics, resolvePrefix));
|
|
3626
3731
|
}
|
|
3627
3732
|
return result;
|
|
3628
3733
|
}
|
|
@@ -3631,6 +3736,7 @@ function lowerField(
|
|
|
3631
3736
|
row: Entry,
|
|
3632
3737
|
aliases: ReadonlyMap<string, Expr>,
|
|
3633
3738
|
diagnostics: GeneralDiagnostic[],
|
|
3739
|
+
resolvePrefix?: (target: string) => string | undefined,
|
|
3634
3740
|
): TypedField {
|
|
3635
3741
|
const name = camel(row.key.name);
|
|
3636
3742
|
let typeExpr = row.value;
|
|
@@ -3692,6 +3798,21 @@ function lowerField(
|
|
|
3692
3798
|
severity: "error",
|
|
3693
3799
|
span: declaredSpan,
|
|
3694
3800
|
});
|
|
3801
|
+
} else if (type.kind === "ref" && type.target) {
|
|
3802
|
+
const prefix = resolvePrefix?.(type.target);
|
|
3803
|
+
if (!extra.pattern) {
|
|
3804
|
+
if (prefix) {
|
|
3805
|
+
extra.pattern = `^${prefix}_(sandbox|live)_[a-z0-9]{8,64}$`;
|
|
3806
|
+
} else if (resolvePrefix !== undefined) {
|
|
3807
|
+
diagnostics.push({
|
|
3808
|
+
code: "HSX1007",
|
|
3809
|
+
fix: "reference an instrument declared in this program, imported from std, or published in the catalogue",
|
|
3810
|
+
message: `field ${name} references unknown instrument ${type.target}`,
|
|
3811
|
+
severity: "error",
|
|
3812
|
+
span: declaredSpan,
|
|
3813
|
+
});
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3695
3816
|
}
|
|
3696
3817
|
|
|
3697
3818
|
const structuralType = Object.hasOwn(extra, "items")
|
|
@@ -4174,9 +4295,38 @@ function bindFieldReferences(
|
|
|
4174
4295
|
): void {
|
|
4175
4296
|
const externalFieldSlot = (path: readonly string[]): boolean => {
|
|
4176
4297
|
const key = path.at(-1);
|
|
4298
|
+
const input =
|
|
4299
|
+
slots.input !== undefined && isJsonObject(slots.input)
|
|
4300
|
+
? slots.input
|
|
4301
|
+
: undefined;
|
|
4302
|
+
const allocation =
|
|
4303
|
+
slots.allocate !== undefined && isJsonObject(slots.allocate)
|
|
4304
|
+
? slots.allocate
|
|
4305
|
+
: undefined;
|
|
4306
|
+
const allocationOperand =
|
|
4307
|
+
path.includes("allocate") &&
|
|
4308
|
+
["amountField", "sourceAccountField", "paymentIdentityField"].includes(
|
|
4309
|
+
key ?? "",
|
|
4310
|
+
);
|
|
4177
4311
|
return (
|
|
4312
|
+
(allocationOperand &&
|
|
4313
|
+
Array.isArray(input?.required) &&
|
|
4314
|
+
allocation !== undefined &&
|
|
4315
|
+
input.required.includes(allocation[key!]!)) ||
|
|
4316
|
+
(path.includes("funding") &&
|
|
4317
|
+
[
|
|
4318
|
+
"ticketRefField",
|
|
4319
|
+
"ticketAmountField",
|
|
4320
|
+
"ticketInvestorField",
|
|
4321
|
+
"ticketAccountField",
|
|
4322
|
+
].includes(key ?? "")) ||
|
|
4323
|
+
(path.includes("requiresExposure") && key === "minimumField") ||
|
|
4178
4324
|
(path.includes("requiresAggregate") && key === "refField") ||
|
|
4179
|
-
(path.includes("requiresAggregate") &&
|
|
4325
|
+
(path.includes("requiresAggregate") &&
|
|
4326
|
+
["amountField", "dateField", "positionField"].includes(key ?? "")) ||
|
|
4327
|
+
(path.includes("requiresAggregate") &&
|
|
4328
|
+
path.includes("dueBefore") &&
|
|
4329
|
+
key === "field") ||
|
|
4180
4330
|
(path.includes("signedSum") && key === "refField") ||
|
|
4181
4331
|
(path.includes("signedSum") && key === "amountField") ||
|
|
4182
4332
|
(path.includes("remainder") && key === "refField") ||
|
|
@@ -4220,6 +4370,7 @@ function bindFieldReferences(
|
|
|
4220
4370
|
function crossInstrumentReferenceDiagnostics(
|
|
4221
4371
|
instruments: readonly TypedInstrument[],
|
|
4222
4372
|
diagnostics: GeneralDiagnostic[],
|
|
4373
|
+
knownTargets?: ReadonlySet<string>,
|
|
4223
4374
|
): void {
|
|
4224
4375
|
const byId = new Map(
|
|
4225
4376
|
instruments.map((instrument) => [instrument.id, instrument]),
|
|
@@ -4254,7 +4405,10 @@ function crossInstrumentReferenceDiagnostics(
|
|
|
4254
4405
|
for (const owner of instruments) {
|
|
4255
4406
|
for (const field of owner.fields) {
|
|
4256
4407
|
if (field.type.kind !== "ref" || !field.type.target) continue;
|
|
4257
|
-
if (
|
|
4408
|
+
if (
|
|
4409
|
+
!byId.has(field.type.target) &&
|
|
4410
|
+
!knownTargets?.has(field.type.target)
|
|
4411
|
+
) {
|
|
4258
4412
|
report(
|
|
4259
4413
|
owner,
|
|
4260
4414
|
field.origin,
|
|
@@ -4367,6 +4521,23 @@ function crossInstrumentReferenceDiagnostics(
|
|
|
4367
4521
|
return;
|
|
4368
4522
|
}
|
|
4369
4523
|
const refField = relation.refField;
|
|
4524
|
+
const anchorName =
|
|
4525
|
+
relation.anchorField ??
|
|
4526
|
+
(relation.over === "siblings" ? refField : undefined);
|
|
4527
|
+
const anchor =
|
|
4528
|
+
typeof anchorName === "string" ? fieldIn(owner, anchorName) : undefined;
|
|
4529
|
+
const expectedTarget =
|
|
4530
|
+
anchor?.type.kind === "ref" ? anchor.type.target : owner.id;
|
|
4531
|
+
if (
|
|
4532
|
+
typeof anchorName === "string" &&
|
|
4533
|
+
(!anchor || anchor.type.kind !== "ref")
|
|
4534
|
+
)
|
|
4535
|
+
report(
|
|
4536
|
+
owner,
|
|
4537
|
+
action.origin,
|
|
4538
|
+
`aggregate anchor ${anchorName} is not a reference`,
|
|
4539
|
+
"declare the anchor as a typed parent reference",
|
|
4540
|
+
);
|
|
4370
4541
|
if (typeof refField === "string") {
|
|
4371
4542
|
const field = fieldIn(target, refField);
|
|
4372
4543
|
if (!field) {
|
|
@@ -4374,17 +4545,18 @@ function crossInstrumentReferenceDiagnostics(
|
|
|
4374
4545
|
owner,
|
|
4375
4546
|
action.origin,
|
|
4376
4547
|
`${targetId}.${refField} does not exist`,
|
|
4377
|
-
`declare ${refField} on ${targetId} as ref<${
|
|
4548
|
+
`declare ${refField} on ${targetId} as ref<${expectedTarget}> or correct the clause`,
|
|
4378
4549
|
);
|
|
4379
4550
|
} else if (
|
|
4380
4551
|
field.type.kind !== "ref" ||
|
|
4381
|
-
(field.type.target !== undefined &&
|
|
4552
|
+
(field.type.target !== undefined &&
|
|
4553
|
+
field.type.target !== expectedTarget)
|
|
4382
4554
|
) {
|
|
4383
4555
|
report(
|
|
4384
4556
|
owner,
|
|
4385
4557
|
action.origin,
|
|
4386
|
-
`${targetId}.${refField} has ${typeWords(field.type)}; this relation needs ref<${
|
|
4387
|
-
`change ${targetId}.${refField} to ref<${
|
|
4558
|
+
`${targetId}.${refField} has ${typeWords(field.type)}; this relation needs ref<${expectedTarget}>`,
|
|
4559
|
+
`change ${targetId}.${refField} to ref<${expectedTarget}>`,
|
|
4388
4560
|
);
|
|
4389
4561
|
}
|
|
4390
4562
|
}
|
|
@@ -4408,12 +4580,13 @@ function crossInstrumentReferenceDiagnostics(
|
|
|
4408
4580
|
const targetField = check?.targetField;
|
|
4409
4581
|
if (
|
|
4410
4582
|
typeof targetField === "string" &&
|
|
4411
|
-
fieldIn(owner, targetField)?.type.kind !==
|
|
4583
|
+
fieldIn(owner, targetField)?.type.kind !==
|
|
4584
|
+
(check?.kind === "count_at_least" ? "integer" : "money")
|
|
4412
4585
|
) {
|
|
4413
4586
|
report(
|
|
4414
4587
|
owner,
|
|
4415
4588
|
action.origin,
|
|
4416
|
-
`${owner.id}.${targetField} is not declared money`,
|
|
4589
|
+
`${owner.id}.${targetField} is not declared ${check?.kind === "count_at_least" ? "integer" : "money"}`,
|
|
4417
4590
|
`declare ${owner.id}.${targetField} as money<C> or correct the aggregate target`,
|
|
4418
4591
|
);
|
|
4419
4592
|
}
|
package/src/version.ts
CHANGED
package/std/SEMANTICS.md
CHANGED
|
@@ -1,139 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Standard library money flows semantics
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
2026-09-01. The in-place compiler family specs under `test/` are the semantic
|
|
5
|
-
authority. This note is the human porting index.
|
|
3
|
+
This document records the runtime semantics and settlement mechanics of standard library modules under `std.money_flows`. The compiler and its unit specifications under `test/` remain the authoritative implementation reference.
|
|
6
4
|
|
|
7
|
-
## Direct and
|
|
5
|
+
## Direct checkout and card
|
|
8
6
|
|
|
9
|
-
- `instant_transfer` stores the amount, currency, payer, and payee.
|
|
10
|
-
|
|
11
|
-
`on_top` move. A payee fee is carved from the base amount. Exact, basis-point,
|
|
12
|
-
and tiered rules produce fee fields and `feeRules`. The finest common
|
|
13
|
-
refinement of all fee cuts produces piece fields and one partition. Floor
|
|
14
|
-
rounding sends the remainder to the named non-fee recipient. A derived
|
|
15
|
-
amount adds a floor percentage field, `derivedAmounts`, a platform party, and
|
|
16
|
-
its own transfer piece.
|
|
17
|
-
- `held_payment` reserves every refined piece before release. It posts release
|
|
18
|
-
pieces, voids cancellation pieces, and keeps payer service fees outside the
|
|
19
|
-
held principal. Release and cancellation fee sides share the finest common
|
|
20
|
-
partition, so each stored piece has one release recipient and one cancel
|
|
21
|
-
recipient. States track each funding, release, cancellation, and abandonment
|
|
22
|
-
step. Abandonment refunds every still-held piece. A deadline release and a
|
|
23
|
-
caller decision release carry the named date or port clause. Retention is the
|
|
24
|
-
same form with one held piece, a release deadline, and cancellation back to
|
|
25
|
-
the contractor. Whole-amount mode funds the principal and on-top fee in one
|
|
26
|
-
action, then releases or cancels the principal whole. A condition and date
|
|
27
|
-
can coexist, with separate early and scheduled release actions.
|
|
28
|
-
- `captured_payment` stores authorization total, reserve and reversal dates,
|
|
29
|
-
and both party accounts. It moves through `created`, `authorized`, optional
|
|
30
|
-
`partially_captured`, `settled`, `voided`, `expired`, `corrected`, or
|
|
31
|
-
`reversed`. Authorize reserves custody. Capture and capture-more consume the
|
|
32
|
-
authorized balance according to the declared mode. Settle posts the captured
|
|
33
|
-
amount. Void and expiry release the reserve. Correction and external reversal
|
|
34
|
-
use their declared ports and windows. Derived fees use the same floor,
|
|
35
|
-
partition, bearer, and position rules as direct payment.
|
|
36
|
-
- `cancellable_booking` holds the booking amount in escrow away from guest and
|
|
37
|
-
host until the booking ends or is cancelled. Cancellation quotes a penalty
|
|
38
|
-
against the time remaining before the start date, frozen with offer life.
|
|
39
|
-
Lifecycle moves through `created`, `held`, `cancellation_quoted`, `canceled`,
|
|
40
|
-
`settled`, and `completed`. Penalty retention moves penalty funds to host and
|
|
41
|
-
remainder to guest; completion releases full amount to host.
|
|
42
|
-
- `premium_forward` holds premium pieces, then forwards the net pieces after a
|
|
43
|
-
bind decision. It can abandon unbound custody. Policy reference, renewal due,
|
|
44
|
-
endorsement evidence, and lapse actions appear only when declared. Its fee
|
|
45
|
-
partition and floor remainder rules match `held_payment`.
|
|
7
|
+
- `instant_transfer` stores the amount, currency, payer, and payee. Action `create` opens the payment record without moving funds. Action `pay_piece_1` and subsequent piece actions execute the money movements, transitioning to state `paid`. A payer fee executes as an `on_top` move. A payee fee carves out of the base amount. Exact, basis-point, and tiered rules generate fee fields and `feeRules`. The finest common refinement of all fee cuts produces piece fields and one partition. Floor rounding sends minor-unit remainder to the designated non-fee recipient. A derived amount adds a floor percentage field, `derivedAmounts`, a platform party account, and its own transfer piece.
|
|
8
|
+
- `captured_payment` stores authorization total, reserve and reversal dates, and party accounts. The instrument moves through `created`, `authorized`, optional `partially_captured`, `settled`, `voided`, `expired`, `corrected`, or `reversed`. Action `authorize` reserves custody on the payer balance. Actions `capture` and `capture_more` consume the authorized balance using partial posts. Action `settle` posts the captured amount. Actions `void` and `expire` release the reserve. Actions for payee correction and external reversal use their declared condition ports and deadline windows. Derived fees use the same floor, partition, bearer, and position rules as direct payment. Four unused signature knobs (`capture_mode`, `correction_mode`, `negative_position`, `timeout`) are pruned.
|
|
46
9
|
|
|
47
|
-
##
|
|
10
|
+
## Custody and escrow
|
|
48
11
|
|
|
49
|
-
- `
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`paid`, and refuses reopening. Recovery is a separate transfer.
|
|
54
|
-
- `advance` has two forms. A carved advance references a held settlement and
|
|
55
|
-
its recourse settlement, disburses once, and settles from the referenced
|
|
56
|
-
release without minting new value. A scheduled advance stores advance,
|
|
57
|
-
fee, repayable total, first due date, and one repayment field per fixed
|
|
58
|
-
installment. It partitions repayments to the repayable total and advances
|
|
59
|
-
before collecting each due installment.
|
|
60
|
-
- `credit_facility` stores lender, borrower, draw destination, limit, currency,
|
|
61
|
-
and expiry. The facility can freeze and close. Each draw is a generated child
|
|
62
|
-
with amount, facility reference, and obligation reference. Draw admission
|
|
63
|
-
checks the facility state and aggregate limit. Resolution waits for the
|
|
64
|
-
referenced obligation state.
|
|
12
|
+
- `held_payment` reserves every refined piece in a dedicated escrow account before release. It posts release pieces, voids cancellation pieces, and keeps payer service fees outside the held principal. Release and cancellation fee sides share the finest common partition, so each stored piece has one release recipient and one cancel recipient. States track funding, release, cancellation, and abandonment steps. Abandonment refunds every still-held piece. A deadline release and a caller decision release carry the named date or port clause. Retention forms reserve one held piece with a release deadline and cancellation back to the contractor. Whole-amount mode funds principal and on-top fees in one action, then releases or cancels the principal whole. Quoted cancellation uses `cancel_charge_bps` and `cancel_offer_life` to freeze penalty quotes before confirm and retain actions.
|
|
13
|
+
- `cancellable_booking` holds booking funds in escrow away from guest and host until the booking ends or cancels. Cancellation quotes a penalty against the time remaining before `starts_at`, frozen for `offer_life`. Lifecycle moves through `created`, `held`, `cancellation_quoted`, `canceled`, `settled`, and `completed`. Penalty retention moves penalty funds to host and remainder to guest; completion releases full amount to host.
|
|
14
|
+
- `security_deposit` reserves the full deposit amount in escrow. A claim port posts either the whole hold or an externally decided amount bounded by the hold. The explicit remainder returns to the payer. A return port voids the whole hold. The deadline form adds machine expiry and unfunded cancellation.
|
|
15
|
+
- `premium_forward` holds premium pieces in escrow, then forwards net pieces after a carrier bind decision. It can abandon unbound custody. Policy reference, renewal due, endorsement evidence, and lapse actions activate when declared. Fee partition and floor remainder rules match `held_payment`.
|
|
65
16
|
|
|
66
|
-
##
|
|
17
|
+
## Subscriptions, usage, and schedules
|
|
67
18
|
|
|
68
|
-
-
|
|
69
|
-
field per installment.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
installment. The parent stores principal, delinquency dates, party accounts,
|
|
74
|
-
partitions, and aggregate invariants. It supports draft approval, optional
|
|
75
|
-
advance, fixed collection and delinquency actions, write-off, and completion.
|
|
76
|
-
Each child binds parent fields, repays once, and can refund once.
|
|
77
|
-
- `recurring_collection` with a finite count uses the scheduled construction.
|
|
78
|
-
Open recurrence stores one amount and anchor, opens one period at a time,
|
|
79
|
-
collects that period, and permits cancellation. It never unrolls an unbounded
|
|
80
|
-
runtime loop.
|
|
81
|
-
- `metered` stores one money field for each declared meter and a period end.
|
|
82
|
-
Every charge moves directly from payer to payee. The instrument never accrues
|
|
83
|
-
custody. Closing the period only changes state.
|
|
19
|
+
- `scheduled` executes calendar-anchored payment series between one payer and one payee across three operational modes:
|
|
20
|
+
1. Finite installment plan: Stores total, first due date, and one money field per installment. The caller supplies each piece; a partition checks their sum, not equality. States and actions unroll in order with cumulative duration offsets.
|
|
21
|
+
2. Open recurring subscription: Charges a recurring amount on interval `every` until an `until` port fires, opening one period liability at a time.
|
|
22
|
+
3. Debt obligation: Emits a parent obligation plus one child payment instrument per installment. The parent stores principal, delinquency dates, party accounts, and aggregate invariants. Child instruments bind parent fields, repay against installment caps, and support full refunds. Finite obligation count generalization applies without grace periods.
|
|
23
|
+
- `metered` stores one money field for each declared meter in its rate card and a period end date. Each usage charge executes an immediate transfer directly from payer to payee without escrow custody. Closing the period transitions state to `closed` at `close_by` and makes further charges unreachable.
|
|
84
24
|
|
|
85
|
-
##
|
|
25
|
+
## Lending and credit
|
|
86
26
|
|
|
87
|
-
- `
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
- `
|
|
92
|
-
parent stores source, total, record date, and maximum recipient count. A port
|
|
93
|
-
freezes the entitlement snapshot. Child rows store recipient, weight, source,
|
|
94
|
-
currency, and parent reference. Largest-remainder distribution pays children
|
|
95
|
-
once. Aggregate clauses cap and total the child set. Withholding is refused
|
|
96
|
-
and correction creates a new distribution. Its `flat` form emits a single
|
|
97
|
-
claim instead. The caller supplies typed pool, weight, and group references,
|
|
98
|
-
their state gates, copy and match maps, and the pool amount path.
|
|
99
|
-
- `threshold_pool` emits a pool parent and commitment child. The parent stores
|
|
100
|
-
target, close date, maximum contributors, beneficiary, and currency. It moves
|
|
101
|
-
through open, active, failed, or settled based on aggregate commitment gates.
|
|
102
|
-
Each commitment can cancel before collection, collect into the round, or
|
|
103
|
-
refund after failure. Its referenced-contribution form accepts a published
|
|
104
|
-
contribution instrument, beneficiary account, and memo and emits no child.
|
|
105
|
-
- `security_deposit` reserves the full amount. A claim port posts either the
|
|
106
|
-
whole hold or a decided amount bounded by the hold. The explicit remainder
|
|
107
|
-
returns to the payer. A return port voids the whole hold. The deadline form
|
|
108
|
-
adds machine expiry and unfunded cancellation. Claim and return targets can
|
|
109
|
-
name either party.
|
|
27
|
+
- `advance` provides upfront capital disbursed to a recipient and repaid through two distinct modes:
|
|
28
|
+
1. Carved advance: References an active escrow hold and recourse instrument through `against`. It disburses once and settles from the referenced release without minting new value. In this mode, the `fee` parameter is ignored.
|
|
29
|
+
2. Scheduled advance: Stores advance amount, fee amount, repayable total, first due date, and one repayment field per installment. A supplied `fee` derives feeAmount as a floor percentage of principal. The caller supplies principal, repayable total and repayment pieces, which partitions reconcile. `repayment_source` can differ from the capital recipient. `profit_to` splits each collection between principal returned to the funder and profit paid to its recipient. `dated: true` requires one stored signed due date per repayment. Equality, chronological ordering, partial servicing and arrears are not implied by those partitions.
|
|
30
|
+
- `credit_facility` stores lender, borrower, draw destination, credit limit, currency, and expiry date. The facility transitions across `active`, `frozen`, and `closed`. Each draw creates a child instrument with draw amount, facility reference, and obligation reference. Draw admission checks facility active state and aggregate child exposure against the limit. Closure requires all child draws to reach status `resolved`. Unused signature knobs (`availability_policy`, `expiry_policy`, `close_policy`) are pruned.
|
|
31
|
+
- `conditional_disbursement` emits a submitted parent instrument with cap, currency, source account, destination account, and decision port. Denial terminates the parent before child approvals. Child approval instruments store externally bounded amounts. Child actions capture port input, move through `created`, `approved`, and `paid`, and verify aggregate child exposure against the parent cap. Unused signature knobs (`reopen_policy`, `recovery_policy`) are pruned.
|
|
110
32
|
|
|
111
|
-
##
|
|
33
|
+
## Multi-party distributions and pooling
|
|
112
34
|
|
|
113
|
-
- `
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
- `settlement_batch` emits a batch
|
|
122
|
-
|
|
123
|
-
accounts, beneficiary reference, and currency. Close freezes intake.
|
|
124
|
-
Calculate signs capture, fee, and reversal entries. Approve rejects a negative
|
|
125
|
-
position when configured. Instruct, acknowledge, and reconcile use the
|
|
126
|
-
declared payout port. Child references and statuses define every aggregate
|
|
127
|
-
and signed-sum input.
|
|
128
|
-
- `rotating_pool` emits one parent plus one contribution child per fixed member.
|
|
129
|
-
The parent unrolls active and ready states for every cycle and checks that each
|
|
130
|
-
member row exists exactly once. Every child unrolls due, funded, defaulted,
|
|
131
|
-
guaranteed, paid, and completed states for every cycle. Due offsets scale the
|
|
132
|
-
fixed recurrence. A funded or guaranteed contribution pays the roster's fixed
|
|
133
|
-
beneficiary for that cycle. The final close requires the escrow account to be
|
|
134
|
-
drained. Its referenced-membership form delegates those member cycles to a
|
|
135
|
-
published membership instrument and emits only the parent.
|
|
136
|
-
- `reconciled_payout` instructs an external payout with a tolerance dial,
|
|
137
|
-
waits for a matching bank debit statement line, and treats unmatched amounts
|
|
138
|
-
at the settle date as break rows. Lifecycle moves through `created`,
|
|
139
|
-
`instructed`, and `settled`.
|
|
35
|
+
- `pooled_split` stores pool total, due date, and one share amount and account per recipient. Positive shares total exactly 10,000 basis points. Floor division assigns each share, and the designated remainder recipient absorbs minor-unit residues. Funding and distribution actions execute in roster order.
|
|
36
|
+
- `weighted_distribution` emits a parent distribution instrument and child entitlement instruments. The parent stores source account, total amount, record date, and maximum recipient count. Child rows store recipient, weight, source, currency, and parent reference. A largest-remainder distribution algorithm pays children once. Aggregate clauses cap and total child records against the pool.
|
|
37
|
+
- `threshold_pool` emits a pool parent instrument and child commitment instruments. The parent stores target amount, close date, maximum contributors, beneficiary, and currency. It transitions through `open`, `active`, `failed`, or `settled` based on aggregate commitment gates. Each commitment can cancel before collection, collect into the pool, or refund after campaign failure.
|
|
38
|
+
- `rotating_pool` emits a parent pool instrument plus one contribution child instrument per member. The parent unrolls active and ready states for every cycle and checks that each member row exists exactly once. Member child instruments unroll due, funded, defaulted, guaranteed, paid, and completed states for every cycle. A funded or guaranteed contribution pays the fixed beneficiary for that cycle. Final closure requires the escrow balance to drain to zero.
|
|
39
|
+
|
|
40
|
+
## Bilateral exchange, clearing, and reconciliation
|
|
41
|
+
|
|
42
|
+
- `swap` stores amounts, accounts, fees, currency, and optional clawback dates for both sides. Funding is atomic across both sides. Release, settlement, cancellation, dispute, abandonment, and clawback preserve the two-sided conservation group. Side fees retain their declared bearer and position. Unwind refunds each side and applies declared penalty tiers without altering principal partitions.
|
|
43
|
+
- `settlement_batch` emits a batch instrument alongside capture, credit-adjustment, and debit-adjustment child records. The batch stores close time, settlement and payout accounts, beneficiary reference, and currency. Closure freezes intake. Calculation applies signed arithmetic across capture, fee, and reversal entries. Actions instruct, acknowledge, and reconcile through the declared payout port.
|
|
44
|
+
- `reconciled_payout` instructs an external payout with a tolerance window, waits for a matching bank debit statement line, and tracks unmatched amounts at settlement date as break rows. Lifecycle transitions through `created`, `instructed`, and `settled`.
|