@hyperscale0/hsx 2.1.1 → 2.2.1

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 (110) hide show
  1. package/CHANGELOG.md +38 -7
  2. package/dist/src/cli.d.ts +1 -1
  3. package/dist/src/cli.d.ts.map +1 -1
  4. package/dist/src/cli.js +39 -5
  5. package/dist/src/cli.js.map +1 -1
  6. package/dist/src/compile.d.ts +0 -1
  7. package/dist/src/compile.d.ts.map +1 -1
  8. package/dist/src/compile.js +15 -1
  9. package/dist/src/compile.js.map +1 -1
  10. package/dist/src/cost.d.ts +3 -3
  11. package/dist/src/cost.d.ts.map +1 -1
  12. package/dist/src/cost.js +22 -12
  13. package/dist/src/cost.js.map +1 -1
  14. package/dist/src/parse.d.ts.map +1 -1
  15. package/dist/src/parse.js +10 -14
  16. package/dist/src/parse.js.map +1 -1
  17. package/dist/src/std-bundle.d.ts.map +1 -1
  18. package/dist/src/std-bundle.js +20 -84
  19. package/dist/src/std-bundle.js.map +1 -1
  20. package/dist/src/typecheck.d.ts.map +1 -1
  21. package/dist/src/typecheck.js +171 -39
  22. package/dist/src/typecheck.js.map +1 -1
  23. package/dist/src/version.d.ts +1 -1
  24. package/dist/src/version.js +1 -1
  25. package/docs/README.md +4 -2
  26. package/docs/guide/01-first-program.md +1 -1
  27. package/docs/guide/03-instruments.md +14 -1
  28. package/docs/guide/06-schedules.md +5 -1
  29. package/docs/guide/08-writing-a-module.md +48 -0
  30. package/docs/llms-full.txt +1966 -422
  31. package/docs/llms.txt +3 -4
  32. package/docs/reference/cli.md +6 -5
  33. package/docs/reference/diagnostics.md +9 -9
  34. package/docs/reference/grammar.md +2 -3
  35. package/docs/reference/std/advance.md +94 -17
  36. package/docs/reference/std/cancellable_booking.md +138 -17
  37. package/docs/reference/std/captured_payment.md +96 -24
  38. package/docs/reference/std/conditional_disbursement.md +82 -14
  39. package/docs/reference/std/credit_facility.md +89 -16
  40. package/docs/reference/std/held_payment.md +155 -55
  41. package/docs/reference/std/instant_transfer.md +79 -12
  42. package/docs/reference/std/metered.md +71 -9
  43. package/docs/reference/std/pooled_split.md +76 -7
  44. package/docs/reference/std/premium_forward.md +100 -20
  45. package/docs/reference/std/reconciled_payout.md +84 -14
  46. package/docs/reference/std/rotating_pool.md +112 -24
  47. package/docs/reference/std/scheduled.md +117 -32
  48. package/docs/reference/std/security_deposit.md +119 -27
  49. package/docs/reference/std/settlement_batch.md +105 -24
  50. package/docs/reference/std/swap.md +113 -26
  51. package/docs/reference/std/threshold_pool.md +120 -29
  52. package/docs/reference/std/weighted_distribution.md +117 -21
  53. package/docs/reference/types.md +39 -15
  54. package/docs/reference/udl-output.md +6 -6
  55. package/examples/01-first-program/README.md +1 -1
  56. package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/README.md +1 -1
  57. package/examples/{02-imports-and-archetypes → 02-imports-and-modules}/photo-booth.hsx +1 -1
  58. package/examples/04-complete-product/README.md +1 -1
  59. package/examples/05-authored-instrument/README.md +5 -0
  60. package/examples/05-authored-instrument/payment.hsx +37 -0
  61. package/examples/05-watch-club/watch-club.hsx +0 -1
  62. package/examples/README.md +1 -2
  63. package/examples/advance/advance.udl +29 -6
  64. package/examples/cancellable_booking/cancellable_booking.udl +19 -0
  65. package/examples/captured_payment/captured_payment.hsx +0 -4
  66. package/examples/captured_payment/captured_payment.udl +6 -0
  67. package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -2
  68. package/examples/conditional_disbursement/conditional_disbursement.udl +4 -0
  69. package/examples/cost-table.json +136 -8
  70. package/examples/credit_facility/credit_facility.hsx +0 -3
  71. package/examples/credit_facility/credit_facility.udl +17 -1
  72. package/examples/held_payment/held_payment.udl +16 -0
  73. package/examples/instant_transfer/instant_transfer.udl +6 -0
  74. package/examples/metered/metered.udl +5 -1
  75. package/examples/pooled_split/pooled_split.udl +4 -0
  76. package/examples/premium_forward/premium_forward.udl +4 -0
  77. package/examples/reconciled_payout/reconciled_payout.udl +7 -0
  78. package/examples/rotating_pool/rotating_pool.udl +8 -0
  79. package/examples/scheduled/scheduled.udl +18 -3
  80. package/examples/security_deposit/security_deposit.udl +13 -0
  81. package/examples/settlement_batch/settlement_batch.udl +4 -0
  82. package/examples/swap/swap.udl +10 -0
  83. package/examples/threshold_pool/threshold_pool.udl +11 -0
  84. package/examples/weighted_distribution/weighted_distribution.udl +9 -0
  85. package/package.json +10 -10
  86. package/skills/hsx/SKILL.md +2 -36
  87. package/src/cli.ts +41 -5
  88. package/src/compile.ts +14 -6
  89. package/src/cost.ts +13 -16
  90. package/src/parse.ts +15 -10
  91. package/src/std-bundle.ts +21 -88
  92. package/src/typecheck.ts +207 -34
  93. package/src/version.ts +1 -1
  94. package/std/SEMANTICS.md +33 -128
  95. package/std/money_flows/advance.hsx +39 -26
  96. package/std/money_flows/cancellable_booking.hsx +275 -8
  97. package/std/money_flows/captured_payment.hsx +1 -9
  98. package/std/money_flows/conditional_disbursement.hsx +0 -6
  99. package/std/money_flows/credit_facility.hsx +0 -9
  100. package/std/money_flows/held_payment.hsx +39 -1
  101. package/std/money_flows/index.hsx +2 -1
  102. package/std/money_flows/metered.hsx +1 -5
  103. package/std/money_flows/scheduled.hsx +29 -119
  104. package/std/money_flows/threshold_pool.hsx +40 -5
  105. package/std/money_flows/weighted_distribution.hsx +47 -5
  106. package/docs/reference/std/recurring_collection.md +0 -25
  107. package/examples/recurring_collection/README.md +0 -3
  108. package/examples/recurring_collection/recurring_collection.hsx +0 -21
  109. package/examples/recurring_collection/recurring_collection.udl +0 -1135
  110. package/std/money_flows/recurring_collection.hsx +0 -72
@@ -1 +1 @@
1
- {"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAGnE;IACA;QACE,cAAc;QACd,s/RAAs/R;KACv/R;IACD;QACE,yBAAyB;QACzB,muTAAmuT;KACpuT;IACD;QACE,qCAAqC;QACrC,20TAA20T;KAC50T;IACD;QACE,kCAAkC;QAClC,sgeAAsge;KACvge;IACD;QACE,0CAA0C;QAC1C,+tSAA+tS;KAChuS;IACD;QACE,iCAAiC;QACjC,uwRAAuwR;KACxwR;IACD;QACE,8BAA8B;QAC9B,0yyEAA0yyE;KAC3yyE;IACD;QACE,uBAAuB;QACvB,oGAAoG;KACrG;IACD;QACE,kCAAkC;QAClC,kvfAAkvf;KACnvf;IACD;QACE,yBAAyB;QACzB,w8IAAw8I;KACz8I;IACD;QACE,8BAA8B;QAC9B,y9NAAy9N;KAC19N;IACD;QACE,iCAAiC;QACjC,2ocAA2oc;KAC5oc;IACD;QACE,mCAAmC;QACnC,m6RAAm6R;KACp6R;IACD;QACE,sCAAsC;QACtC,yrGAAyrG;KAC1rG;IACD;QACE,+BAA+B;QAC/B,6nvBAA6nvB;KAC9nvB;IACD;QACE,2BAA2B;QAC3B,63kCAA63kC;KAC93kC;IACD;QACE,kCAAkC;QAClC,wteAAwte;KACzte;IACD;QACE,kCAAkC;QAClC,modAAmod;KACpod;IACD;QACE,sBAAsB;QACtB,8qtCAA8qtC;KAC/qtC;IACD;QACE,gCAAgC;QAChC,ytjBAAytjB;KAC1tjB;IACD;QACE,uCAAuC;QACvC,y7aAAy7a;KAC17a;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,cAAc,EAAE,26SAA26S,CAAC;IAC77S,CAAC,yBAAyB,EAAE,usWAAusW,CAAC;IACpuW,CAAC,qCAAqC,EAAE,y1/BAAy1/B,CAAC;IACl4/B,CAAC,kCAAkC,EAAE,4pdAA4pd,CAAC;IAClsd,CAAC,0CAA0C,EAAE,shSAAshS,CAAC;IACpkS,CAAC,iCAAiC,EAAE,s8QAAs8Q,CAAC;IAC3+Q,CAAC,8BAA8B,EAAE,g73EAAg73E,CAAC;IACl93E,CAAC,uBAAuB,EAAE,8JAA8J,CAAC;IACzL,CAAC,kCAAkC,EAAE,k7fAAk7f,CAAC;IACx9f,CAAC,yBAAyB,EAAE,siIAAsiI,CAAC;IACnkI,CAAC,8BAA8B,EAAE,slOAAslO,CAAC;IACxnO,CAAC,iCAAiC,EAAE,wucAAwuc,CAAC;IAC7wc,CAAC,mCAAmC,EAAE,4hSAA4hS,CAAC;IACnkS,CAAC,+BAA+B,EAAE,ohwBAAohwB,CAAC;IACvjwB,CAAC,2BAA2B,EAAE,gu2BAAgu2B,CAAC;IAC/v2B,CAAC,kCAAkC,EAAE,+0eAA+0e,CAAC;IACr3e,CAAC,kCAAkC,EAAE,s2dAAs2d,CAAC;IAC54d,CAAC,sBAAsB,EAAE,kxuCAAkxuC,CAAC;IAC5yuC,CAAC,gCAAgC,EAAE,ktpBAAktpB,CAAC;IACtvpB,CAAC,uCAAuC,EAAE,mtgBAAmtgB,CAAC;CAC/vgB,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"typecheck.d.ts","sourceRoot":"","sources":["../../src/typecheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAaV,OAAO,EAKR,MAAM,UAAU,CAAC;AASlB,OAAO,KAAK,EACV,kBAAkB,EASnB,MAAM,SAAS,CAAC;AAWjB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;CACzC;AAkCD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAibpB"}
1
+ {"version":3,"file":"typecheck.d.ts","sourceRoot":"","sources":["../../src/typecheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,WAAW,EAEjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAaV,OAAO,EAKR,MAAM,UAAU,CAAC;AASlB,OAAO,KAAK,EACV,kBAAkB,EASnB,MAAM,SAAS,CAAC;AAWjB,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;CACzC;AAkCD,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CA4cpB"}
@@ -1,4 +1,4 @@
1
- import { deriveUdlActionEffects, resolveUdlActionPlans, udlDocumentSchema, udlClauseVocabulary, } from "@hyperscale0/udl";
1
+ import { fixedIsoDurationMs, referencedUdlInstrumentIds, deriveUdlActionEffects, resolveUdlActionPlans, udlDocumentSchema, udlClauseVocabulary, } from "@hyperscale0/udl";
2
2
  import { requiredFields, generalInstrumentValue, hasActionPlanClauses, lowerGeneralProgram, } from "./emit.js";
3
3
  import { hsxClauseDiagnosticCode } from "./diagnostics.js";
4
4
  import { HSX_LIMITS } from "./limits.js";
@@ -250,6 +250,24 @@ export function checkGeneralProgram(program, options = {}) {
250
250
  if (options.publishedCatalog) {
251
251
  checkPortCaptureTypes(allocated, options.publishedCatalog, aliases, diagnostics);
252
252
  }
253
+ const prefixByInstrument = new Map();
254
+ if (options.publishedCatalog) {
255
+ for (const inst of options.publishedCatalog.instruments) {
256
+ if (inst.idPrefix) {
257
+ prefixByInstrument.set(inst.id, inst.idPrefix);
258
+ }
259
+ }
260
+ }
261
+ for (const [name, decl] of templates) {
262
+ const prefix = stringSlot(decl.body, "idPrefix", "id_prefix") ?? prefixFor(name);
263
+ prefixByInstrument.set(name, prefix);
264
+ }
265
+ for (const candidate of allocated) {
266
+ const prefix = stringSlot(candidate.body, "idPrefix", "id_prefix") ??
267
+ prefixFor(candidate.name.name);
268
+ prefixByInstrument.set(candidate.name.name, prefix);
269
+ }
270
+ const resolvePrefix = (target) => prefixByInstrument.get(target);
253
271
  const instruments = [];
254
272
  const ids = new Set();
255
273
  for (const candidate of allocated) {
@@ -263,14 +281,14 @@ export function checkGeneralProgram(program, options = {}) {
263
281
  continue;
264
282
  }
265
283
  const expandedBody = expandComprehensions(candidate.body, diagnostics);
266
- const checked = checkInstrument(candidate.name, expandedBody, candidate.parties, candidate.ports, candidate.aliases, diagnostics);
284
+ const checked = checkInstrument(candidate.name, expandedBody, candidate.parties, candidate.ports, candidate.aliases, diagnostics, resolvePrefix);
267
285
  if (checked)
268
286
  instruments.push(checked);
269
287
  }
270
288
  if (options.publishedCatalog) {
271
289
  return checkPublishedProgram(program, options.publishedCatalog, header, subjects, instruments, diagnostics, report);
272
290
  }
273
- crossInstrumentReferenceDiagnostics(instruments, diagnostics);
291
+ crossInstrumentReferenceDiagnostics(instruments, diagnostics, new Set(templates.keys()));
274
292
  if (header &&
275
293
  instruments.length === 0 &&
276
294
  !diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
@@ -343,6 +361,10 @@ function checkPublishedProgram(program, catalog, header, authoredSubjects, autho
343
361
  report("HSX1021", exposure.span, `${target} is not an action on an explicitly used published instrument or authored instrument`, "add the matching use declaration and choose one of that instrument's actions");
344
362
  continue;
345
363
  }
364
+ if (action?.engineOwned || authoredAction?.slots.engineOwned === true) {
365
+ report("HSX1021", exposure.span, `${target} is engine-owned`, "expose a caller action instead");
366
+ continue;
367
+ }
346
368
  if (!CAMEL_CASE.test(exposure.publicName.name)) {
347
369
  report("HSX1020", exposure.publicName.span, `public action name ${exposure.publicName.name} is not camelCase`, "write a lowercase camelCase public action name");
348
370
  continue;
@@ -466,23 +488,7 @@ function publishedInstrumentClosure(uses, catalogById) {
466
488
  return selected;
467
489
  }
468
490
  function publishedInstrumentDependencies(instrument, idByPrefix) {
469
- const found = new Set();
470
- const visit = (value) => {
471
- if (Array.isArray(value)) {
472
- value.forEach(visit);
473
- return;
474
- }
475
- if (!value || typeof value !== "object")
476
- return;
477
- for (const [key, child] of Object.entries(value)) {
478
- if ((key === "instrumentId" || key === "childInstrumentId") &&
479
- typeof child === "string") {
480
- found.add(child);
481
- }
482
- visit(child);
483
- }
484
- };
485
- visit(instrument);
491
+ const found = new Set(referencedUdlInstrumentIds(instrument));
486
492
  for (const field of Object.values(instrument.fields)) {
487
493
  const prefix = typeof field.pattern === "string"
488
494
  ? /^\^([a-z]{2,8})_\(sandbox\|live\)_/.exec(field.pattern)?.[1]
@@ -673,7 +679,6 @@ const applicationMetadataKeys = new Set([
673
679
  "action",
674
680
  "agent_description",
675
681
  "description",
676
- "journeys",
677
682
  "nav",
678
683
  "navigation",
679
684
  "summary",
@@ -934,7 +939,11 @@ function allocateGeneratedPrefixes(candidates) {
934
939
  }
935
940
  function inferSettlementTypeArgument(template, rows, typeParameter) {
936
941
  for (const parameter of template.parameters) {
937
- const parameterType = parameter.type;
942
+ const declaredType = parameter.type;
943
+ const parameterType = declaredType.kind === "type_apply" &&
944
+ declaredType.callee.name === "optional"
945
+ ? (declaredType.args[0] ?? declaredType)
946
+ : declaredType;
938
947
  if (parameterType.kind !== "type_apply" ||
939
948
  parameterType.callee.name !== "money" ||
940
949
  parameterType.args[0]?.kind !== "ident" ||
@@ -1177,9 +1186,53 @@ function instantiate(template, application, diagnostics, instrumentName, ports,
1177
1186
  if (diagnostics.length > dependencyDiagnosticStart)
1178
1187
  return undefined;
1179
1188
  const body = expandComprehensions(selected, diagnostics, values, portDependencies);
1189
+ const binding = {
1190
+ key: { kind: "ident", name: "template_binding", span: application.span },
1191
+ qualifiers: [],
1192
+ span: application.span,
1193
+ value: {
1194
+ kind: "block",
1195
+ span: application.span,
1196
+ entries: [
1197
+ {
1198
+ key: { kind: "ident", name: "id", span: application.span },
1199
+ qualifiers: [],
1200
+ span: application.span,
1201
+ value: {
1202
+ kind: "string",
1203
+ value: template.name.name,
1204
+ span: application.span,
1205
+ },
1206
+ },
1207
+ {
1208
+ key: { kind: "ident", name: "parameters", span: application.span },
1209
+ qualifiers: [],
1210
+ span: application.span,
1211
+ value: {
1212
+ kind: "block",
1213
+ span: application.span,
1214
+ entries: resolvedArgs.flatMap(({ parameter }) => {
1215
+ const value = values.get(parameter.name.name);
1216
+ return value &&
1217
+ ["string", "number", "boolean"].includes(value.kind)
1218
+ ? [
1219
+ {
1220
+ key: parameter.name,
1221
+ qualifiers: [],
1222
+ span: parameter.span,
1223
+ value,
1224
+ },
1225
+ ]
1226
+ : [];
1227
+ }),
1228
+ },
1229
+ },
1230
+ ],
1231
+ },
1232
+ };
1180
1233
  return {
1181
1234
  ...body,
1182
- entries: body.entries.map((row) => {
1235
+ entries: [...body.entries, binding].map((row) => {
1183
1236
  if (row.key.name !== "fields" || row.value.kind !== "block")
1184
1237
  return row;
1185
1238
  return {
@@ -1663,9 +1716,14 @@ function substituteExpr(expr, values) {
1663
1716
  }
1664
1717
  : {}),
1665
1718
  key: substituteName(entry.key, nestedValues),
1666
- qualifiers: entry.qualifiers.map((qualifier, index) => {
1719
+ qualifiers: entry.qualifiers.flatMap((qualifier, index) => {
1667
1720
  const value = nestedValues.get(qualifier.name);
1668
1721
  const fixed = fixedQualifier(entry.key.name, qualifier.name, index);
1722
+ if (entry.key.name === "on" &&
1723
+ !fixed &&
1724
+ index > 0 &&
1725
+ value?.kind === "list")
1726
+ return value.items.map((item) => nameFromValue(qualifier, item));
1669
1727
  return !fixed && value
1670
1728
  ? nameFromValue(qualifier, value)
1671
1729
  : substituteName(qualifier, nestedValues);
@@ -2094,6 +2152,17 @@ function evaluateCompileTimeCall(expr, resolveMissing = true) {
2094
2152
  : undefined;
2095
2153
  case "basis_points":
2096
2154
  return first?.kind === "percent" ? numeric(first.bps) : undefined;
2155
+ case "shift_date": {
2156
+ const instant = first ? Date.parse(nameText(first)) : NaN;
2157
+ const offset = second ? fixedIsoDurationMs(nameText(second)) : null;
2158
+ if (!Number.isFinite(instant) || offset === null)
2159
+ return text(NONE_SENTINEL);
2160
+ const direction = third && nameText(third) === "before" ? -1 : 1;
2161
+ const shifted = new Date(instant + direction * offset);
2162
+ return Number.isFinite(shifted.getTime())
2163
+ ? text(shifted.toISOString())
2164
+ : text(NONE_SENTINEL);
2165
+ }
2097
2166
  case "names": {
2098
2167
  const count = number(second);
2099
2168
  if (!first || count === undefined || count < 0 || !third)
@@ -2112,6 +2181,13 @@ function evaluateCompileTimeCall(expr, resolveMissing = true) {
2112
2181
  span: expr.span,
2113
2182
  }
2114
2183
  : undefined;
2184
+ case "without":
2185
+ return first?.kind === "list" && second
2186
+ ? {
2187
+ ...first,
2188
+ items: first.items.filter((item) => nameText(item) !== nameText(second)),
2189
+ }
2190
+ : undefined;
2115
2191
  case "concat_lists":
2116
2192
  return expr.args.every((arg) => arg.kind === "list")
2117
2193
  ? {
@@ -2254,9 +2330,14 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
2254
2330
  const substituted = {
2255
2331
  ...row,
2256
2332
  key: substituteName(row.key, locals),
2257
- qualifiers: row.qualifiers.map((qualifier, index) => {
2333
+ qualifiers: row.qualifiers.flatMap((qualifier, index) => {
2258
2334
  const value = locals.get(qualifier.name);
2259
2335
  const fixed = fixedQualifier(row.key.name, qualifier.name, index);
2336
+ if (row.key.name === "on" &&
2337
+ !fixed &&
2338
+ index > 0 &&
2339
+ value?.kind === "list")
2340
+ return value.items.map((item) => nameFromValue(qualifier, item));
2260
2341
  return !fixed && value
2261
2342
  ? nameFromValue(qualifier, value)
2262
2343
  : substituteName(qualifier, locals);
@@ -2327,7 +2408,7 @@ function finiteIterationValues(bound) {
2327
2408
  span: bound.span,
2328
2409
  }));
2329
2410
  }
2330
- function checkInstrument(name, body, parties, ports, aliases, diagnostics) {
2411
+ function checkInstrument(name, body, parties, ports, aliases, diagnostics, resolvePrefix) {
2331
2412
  if (!SNAKE_CASE.test(name.name)) {
2332
2413
  diagnostics.push({
2333
2414
  code: "HSX1003",
@@ -2339,7 +2420,7 @@ function checkInstrument(name, body, parties, ports, aliases, diagnostics) {
2339
2420
  }
2340
2421
  const fieldsEntry = entry(body, "fields");
2341
2422
  const fields = fieldsEntry?.value.kind === "block"
2342
- ? checkFields(fieldsEntry.value, aliases, diagnostics)
2423
+ ? checkFields(fieldsEntry.value, aliases, diagnostics, resolvePrefix)
2343
2424
  : [];
2344
2425
  if (!fieldsEntry) {
2345
2426
  diagnostics.push({
@@ -2749,7 +2830,7 @@ function checkRequiredFields(value, fields, span, diagnostics) {
2749
2830
  }
2750
2831
  return valid ? value : undefined;
2751
2832
  }
2752
- function checkFields(block, aliases, diagnostics) {
2833
+ function checkFields(block, aliases, diagnostics, resolvePrefix) {
2753
2834
  const result = [];
2754
2835
  const names = new Set();
2755
2836
  for (const row of block.entries) {
@@ -2774,11 +2855,11 @@ function checkFields(block, aliases, diagnostics) {
2774
2855
  continue;
2775
2856
  }
2776
2857
  names.add(name);
2777
- result.push(lowerField(row, aliases, diagnostics));
2858
+ result.push(lowerField(row, aliases, diagnostics, resolvePrefix));
2778
2859
  }
2779
2860
  return result;
2780
2861
  }
2781
- function lowerField(row, aliases, diagnostics) {
2862
+ function lowerField(row, aliases, diagnostics, resolvePrefix) {
2782
2863
  const name = camel(row.key.name);
2783
2864
  let typeExpr = row.value;
2784
2865
  let description;
@@ -2830,6 +2911,23 @@ function lowerField(row, aliases, diagnostics) {
2830
2911
  span: declaredSpan,
2831
2912
  });
2832
2913
  }
2914
+ else if (type.kind === "ref" && type.target) {
2915
+ const prefix = resolvePrefix?.(type.target);
2916
+ if (!extra.pattern) {
2917
+ if (prefix) {
2918
+ extra.pattern = `^${prefix}_(sandbox|live)_[a-z0-9]{8,64}$`;
2919
+ }
2920
+ else if (resolvePrefix !== undefined) {
2921
+ diagnostics.push({
2922
+ code: "HSX1007",
2923
+ fix: "reference an instrument declared in this program, imported from std, or published in the catalogue",
2924
+ message: `field ${name} references unknown instrument ${type.target}`,
2925
+ severity: "error",
2926
+ span: declaredSpan,
2927
+ });
2928
+ }
2929
+ }
2930
+ }
2833
2931
  const structuralType = Object.hasOwn(extra, "items")
2834
2932
  ? "array"
2835
2933
  : Object.hasOwn(extra, "properties") ||
@@ -3212,8 +3310,32 @@ function pinFixedCurrencyBindings(action, fields, diagnostics) {
3212
3310
  function bindFieldReferences(slots, fields, action, diagnostics) {
3213
3311
  const externalFieldSlot = (path) => {
3214
3312
  const key = path.at(-1);
3215
- return ((path.includes("requiresAggregate") && key === "refField") ||
3216
- (path.includes("requiresAggregate") && key === "amountField") ||
3313
+ const input = slots.input !== undefined && isJsonObject(slots.input)
3314
+ ? slots.input
3315
+ : undefined;
3316
+ const allocation = slots.allocate !== undefined && isJsonObject(slots.allocate)
3317
+ ? slots.allocate
3318
+ : undefined;
3319
+ const allocationOperand = path.includes("allocate") &&
3320
+ ["amountField", "sourceAccountField", "paymentIdentityField"].includes(key ?? "");
3321
+ return ((allocationOperand &&
3322
+ Array.isArray(input?.required) &&
3323
+ allocation !== undefined &&
3324
+ input.required.includes(allocation[key])) ||
3325
+ (path.includes("funding") &&
3326
+ [
3327
+ "ticketRefField",
3328
+ "ticketAmountField",
3329
+ "ticketInvestorField",
3330
+ "ticketAccountField",
3331
+ ].includes(key ?? "")) ||
3332
+ (path.includes("requiresExposure") && key === "minimumField") ||
3333
+ (path.includes("requiresAggregate") && key === "refField") ||
3334
+ (path.includes("requiresAggregate") &&
3335
+ ["amountField", "dateField", "positionField"].includes(key ?? "")) ||
3336
+ (path.includes("requiresAggregate") &&
3337
+ path.includes("dueBefore") &&
3338
+ key === "field") ||
3217
3339
  (path.includes("signedSum") && key === "refField") ||
3218
3340
  (path.includes("signedSum") && key === "amountField") ||
3219
3341
  (path.includes("remainder") && key === "refField") ||
@@ -3248,7 +3370,7 @@ function bindFieldReferences(slots, fields, action, diagnostics) {
3248
3370
  };
3249
3371
  visit(slots);
3250
3372
  }
3251
- function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
3373
+ function crossInstrumentReferenceDiagnostics(instruments, diagnostics, knownTargets) {
3252
3374
  const byId = new Map(instruments.map((instrument) => [instrument.id, instrument]));
3253
3375
  const report = (owner, span, message, fix) => {
3254
3376
  diagnostics.push({
@@ -3270,7 +3392,8 @@ function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
3270
3392
  for (const field of owner.fields) {
3271
3393
  if (field.type.kind !== "ref" || !field.type.target)
3272
3394
  continue;
3273
- if (!byId.has(field.type.target)) {
3395
+ if (!byId.has(field.type.target) &&
3396
+ !knownTargets?.has(field.type.target)) {
3274
3397
  report(owner, field.origin, `field ${field.name} has ref<${field.type.target}>, but that instrument does not exist`, `declare instrument ${field.type.target} or correct the ref target`);
3275
3398
  }
3276
3399
  }
@@ -3333,14 +3456,22 @@ function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
3333
3456
  return;
3334
3457
  }
3335
3458
  const refField = relation.refField;
3459
+ const anchorName = relation.anchorField ??
3460
+ (relation.over === "siblings" ? refField : undefined);
3461
+ const anchor = typeof anchorName === "string" ? fieldIn(owner, anchorName) : undefined;
3462
+ const expectedTarget = anchor?.type.kind === "ref" ? anchor.type.target : owner.id;
3463
+ if (typeof anchorName === "string" &&
3464
+ (!anchor || anchor.type.kind !== "ref"))
3465
+ report(owner, action.origin, `aggregate anchor ${anchorName} is not a reference`, "declare the anchor as a typed parent reference");
3336
3466
  if (typeof refField === "string") {
3337
3467
  const field = fieldIn(target, refField);
3338
3468
  if (!field) {
3339
- report(owner, action.origin, `${targetId}.${refField} does not exist`, `declare ${refField} on ${targetId} as ref<${owner.id}> or correct the clause`);
3469
+ report(owner, action.origin, `${targetId}.${refField} does not exist`, `declare ${refField} on ${targetId} as ref<${expectedTarget}> or correct the clause`);
3340
3470
  }
3341
3471
  else if (field.type.kind !== "ref" ||
3342
- (field.type.target !== undefined && field.type.target !== owner.id)) {
3343
- report(owner, action.origin, `${targetId}.${refField} has ${typeWords(field.type)}; this relation needs ref<${owner.id}>`, `change ${targetId}.${refField} to ref<${owner.id}>`);
3472
+ (field.type.target !== undefined &&
3473
+ field.type.target !== expectedTarget)) {
3474
+ report(owner, action.origin, `${targetId}.${refField} has ${typeWords(field.type)}; this relation needs ref<${expectedTarget}>`, `change ${targetId}.${refField} to ref<${expectedTarget}>`);
3344
3475
  }
3345
3476
  }
3346
3477
  const check = jsonObject(relation.check);
@@ -3355,8 +3486,9 @@ function crossInstrumentReferenceDiagnostics(instruments, diagnostics) {
3355
3486
  }
3356
3487
  const targetField = check?.targetField;
3357
3488
  if (typeof targetField === "string" &&
3358
- fieldIn(owner, targetField)?.type.kind !== "money") {
3359
- report(owner, action.origin, `${owner.id}.${targetField} is not declared money`, `declare ${owner.id}.${targetField} as money<C> or correct the aggregate target`);
3489
+ fieldIn(owner, targetField)?.type.kind !==
3490
+ (check?.kind === "count_at_least" ? "integer" : "money")) {
3491
+ report(owner, action.origin, `${owner.id}.${targetField} is not declared ${check?.kind === "count_at_least" ? "integer" : "money"}`, `declare ${owner.id}.${targetField} as money<C> or correct the aggregate target`);
3360
3492
  }
3361
3493
  const statuses = relation.statuses;
3362
3494
  if (Array.isArray(statuses)) {