@hyperscale0/hsx 2.4.0 → 2.4.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 (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/src/cli.d.ts +1 -1
  3. package/dist/src/typecheck.d.ts.map +1 -1
  4. package/dist/src/typecheck.js +114 -85
  5. package/dist/src/typecheck.js.map +1 -1
  6. package/dist/src/version.d.ts +1 -1
  7. package/dist/src/version.js +1 -1
  8. package/docs/llms-full.txt +2 -2
  9. package/docs/llms.txt +2 -2
  10. package/docs/reference/cli.md +2 -2
  11. package/docs/reference/diagnostics.md +1 -1
  12. package/docs/reference/grammar.md +1 -1
  13. package/docs/reference/std/advance.md +1 -1
  14. package/docs/reference/std/cancellable_booking.md +1 -1
  15. package/docs/reference/std/captured_payment.md +1 -1
  16. package/docs/reference/std/conditional_disbursement.md +1 -1
  17. package/docs/reference/std/credit_facility.md +1 -1
  18. package/docs/reference/std/held_payment.md +1 -1
  19. package/docs/reference/std/instant_transfer.md +1 -1
  20. package/docs/reference/std/metered.md +1 -1
  21. package/docs/reference/std/pooled_split.md +1 -1
  22. package/docs/reference/std/premium_forward.md +1 -1
  23. package/docs/reference/std/reconciled_payout.md +1 -1
  24. package/docs/reference/std/rotating_pool.md +1 -1
  25. package/docs/reference/std/scheduled.md +1 -1
  26. package/docs/reference/std/security_deposit.md +1 -1
  27. package/docs/reference/std/settlement_batch.md +1 -1
  28. package/docs/reference/std/swap.md +1 -1
  29. package/docs/reference/std/threshold_pool.md +1 -1
  30. package/docs/reference/std/weighted_distribution.md +1 -1
  31. package/docs/reference/types.md +1 -1
  32. package/docs/reference/udl-output.md +1 -1
  33. package/package.json +2 -2
  34. package/src/typecheck.ts +221 -87
  35. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  The IR format version (`"hsx": 1`, stamped into every compiled document) moves
8
8
  independently of the package version.
9
9
 
10
+ ## [2.4.1] - 2026-09-17
11
+
12
+ Pairs with UDL 2.6.0.
13
+
14
+ ### Fixed
15
+
16
+ - `prefix()` resolves declared instrument `id_prefix` from the catalogue or authored program when known to compilation, falling back to derived initials only when undeclared.
17
+
10
18
  ## [2.4.0] - 2026-09-17
11
19
 
12
20
  Pairs with UDL 2.6.0.
package/dist/src/cli.d.ts CHANGED
@@ -18,6 +18,6 @@ export interface Io {
18
18
  readonly stdin?: NodeJS.ReadableStream;
19
19
  readonly stdout?: NodeJS.WritableStream;
20
20
  }
21
- export declare const USAGE_TEXT = "hsx 2.4.0, the HSX compiler\n\nUsage:\n hsx check <file.hsx> [--catalog <catalog.udl>] [--strict]\n hsx build <file.hsx> [--catalog <catalog.udl>] [--out <file.json>] [--strict]\n hsx cost <file.hsx> [--catalog <catalog.udl>] [--json] [--out <file.json>] [--strict]\n hsx explain <HSX####>\n hsx format <file.hsx>\n hsx lsp\n hsx --version\n hsx --help\n\nCommands:\n check Compile and report diagnostics. Prints nothing when the program is clean.\n build Compile and write canonical UDL as JSON.\n cost Compile and print the version-pinned cost manifest as a table or JSON.\n explain Print one diagnostic title, fix, and source example.\n format Print the source in the one canonical HSX style.\n lsp Run the language server over stdin and stdout.\n\nOptions:\n --catalog <file> Read the published instrument catalogue as canonical UDL JSON.\n --json Print the cost manifest as JSON instead of a table.\n --out <file> Write build or cost JSON to this path instead of stdout.\n --strict Treat warning-severity diagnostics as failures.\n\nExit codes:\n 0 the program compiled (verdict valid, or warning without --strict)\n 1 the program was refused (verdict invalid, or warning with --strict)\n 2 the command line or the input file could not be used";
21
+ export declare const USAGE_TEXT = "hsx 2.4.1, the HSX compiler\n\nUsage:\n hsx check <file.hsx> [--catalog <catalog.udl>] [--strict]\n hsx build <file.hsx> [--catalog <catalog.udl>] [--out <file.json>] [--strict]\n hsx cost <file.hsx> [--catalog <catalog.udl>] [--json] [--out <file.json>] [--strict]\n hsx explain <HSX####>\n hsx format <file.hsx>\n hsx lsp\n hsx --version\n hsx --help\n\nCommands:\n check Compile and report diagnostics. Prints nothing when the program is clean.\n build Compile and write canonical UDL as JSON.\n cost Compile and print the version-pinned cost manifest as a table or JSON.\n explain Print one diagnostic title, fix, and source example.\n format Print the source in the one canonical HSX style.\n lsp Run the language server over stdin and stdout.\n\nOptions:\n --catalog <file> Read the published instrument catalogue as canonical UDL JSON.\n --json Print the cost manifest as JSON instead of a table.\n --out <file> Write build or cost JSON to this path instead of stdout.\n --strict Treat warning-severity diagnostics as failures.\n\nExit codes:\n 0 the program compiled (verdict valid, or warning without --strict)\n 1 the program was refused (verdict invalid, or warning with --strict)\n 2 the command line or the input file could not be used";
22
22
  export declare function runCli(argv: readonly string[], io: Io): Promise<number>;
23
23
  //# sourceMappingURL=cli.d.ts.map
@@ -1 +1 @@
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;AAajB,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
+ {"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;AAajB,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,CAufpB"}
@@ -139,15 +139,33 @@ export function checkGeneralProgram(program, options = {}) {
139
139
  else
140
140
  templates.set(decl.name.name, decl);
141
141
  }
142
+ const prefixByInstrument = new Map();
143
+ if (options.publishedCatalog) {
144
+ for (const inst of options.publishedCatalog.instruments) {
145
+ if (inst.idPrefix) {
146
+ prefixByInstrument.set(inst.id, inst.idPrefix);
147
+ }
148
+ }
149
+ }
150
+ for (const [name, decl] of templates) {
151
+ const prefix = stringSlot(decl.body, "idPrefix", "id_prefix") ?? prefixFor(name);
152
+ prefixByInstrument.set(name, prefix);
153
+ }
154
+ for (const decl of program.decls) {
155
+ if (decl.kind === "instrument_apply" && decl.metadata) {
156
+ const prefix = stringSlot(decl.metadata, "idPrefix", "id_prefix");
157
+ if (prefix) {
158
+ prefixByInstrument.set(decl.name.name, prefix);
159
+ }
160
+ }
161
+ }
162
+ const resolvePrefix = (target) => prefixByInstrument.get(target);
142
163
  const concrete = [];
143
164
  const reachedPorts = new Set();
144
165
  const droppedPorts = new Set();
145
166
  const scopedDeclarations = (declarationScope) => {
146
167
  const scopedAliases = new Map(aliases);
147
- const scopedConstants = resolveConstants([
148
- ...program.decls,
149
- ...declarationScope,
150
- ]);
168
+ const scopedConstants = resolveConstants([...program.decls, ...declarationScope], resolvePrefix);
151
169
  const scopedParties = new Set(parties);
152
170
  const scopedPorts = new Map(ports);
153
171
  const scopedTemplates = new Map(templates);
@@ -160,6 +178,9 @@ export function checkGeneralProgram(program, options = {}) {
160
178
  scopedPorts.set(local.name.name, local);
161
179
  if (local.kind === "instrument") {
162
180
  scopedTemplates.set(local.name.name, local);
181
+ const prefix = stringSlot(local.body, "idPrefix", "id_prefix") ??
182
+ prefixFor(local.name.name);
183
+ prefixByInstrument.set(local.name.name, prefix);
163
184
  }
164
185
  }
165
186
  checkPortDeclarations(scopedPorts, scopedParties);
@@ -189,9 +210,15 @@ export function checkGeneralProgram(program, options = {}) {
189
210
  values.set(name, reference);
190
211
  bindPortCompileValues(values, name, reference, scoped.ports, diagnostics, scoped.aliases, shapes, dependencies);
191
212
  }
192
- const selected = selectCompileTimeRows(decl.body, values, diagnostics, dependencies);
193
- const body = expandComprehensions(selected, diagnostics, values, dependencies);
194
- concrete.push(...bindActionPorts(constructedInstruments(decl.name, body, diagnostics), scoped.ports, reachedPorts).map((candidate) => ({
213
+ const selected = selectCompileTimeRows(decl.body, values, diagnostics, dependencies, [], resolvePrefix);
214
+ const body = expandComprehensions(selected, diagnostics, values, dependencies, resolvePrefix);
215
+ const constructed = constructedInstruments(decl.name, body, diagnostics, resolvePrefix);
216
+ for (const candidate of constructed) {
217
+ const prefix = stringSlot(candidate.body, "idPrefix", "id_prefix") ??
218
+ prefixFor(candidate.name.name);
219
+ prefixByInstrument.set(candidate.name.name, prefix);
220
+ }
221
+ concrete.push(...bindActionPorts(constructed, scoped.ports, reachedPorts).map((candidate) => ({
195
222
  ...candidate,
196
223
  aliases: scoped.aliases,
197
224
  callee: candidate.name.name,
@@ -216,13 +243,19 @@ export function checkGeneralProgram(program, options = {}) {
216
243
  continue;
217
244
  }
218
245
  const boundPorts = new Map();
219
- const body = instantiate(template, application, diagnostics, decl.name, scoped.ports, scoped.parties, scoped.aliases, scoped.constants, boundPorts);
246
+ const body = instantiate(template, application, diagnostics, decl.name, scoped.ports, scoped.parties, scoped.aliases, scoped.constants, boundPorts, resolvePrefix);
220
247
  if (body) {
221
248
  const applicationPorts = new Set();
222
249
  const merged = decl.metadata
223
250
  ? mergeApplicationMetadata(body, decl.metadata, diagnostics)
224
251
  : body;
225
- concrete.push(...bindActionPorts(constructedInstruments(decl.name, merged, diagnostics), boundPorts, applicationPorts).map((candidate) => {
252
+ const constructed = constructedInstruments(decl.name, merged, diagnostics, resolvePrefix);
253
+ for (const candidate of constructed) {
254
+ const prefix = stringSlot(candidate.body, "idPrefix", "id_prefix") ??
255
+ prefixFor(candidate.name.name);
256
+ prefixByInstrument.set(candidate.name.name, prefix);
257
+ }
258
+ concrete.push(...bindActionPorts(constructed, boundPorts, applicationPorts).map((candidate) => {
226
259
  let candidateCallee = callee;
227
260
  if (candidate.name.name !== decl.name.name &&
228
261
  candidate.name.name.startsWith(`${decl.name.name}_`)) {
@@ -252,24 +285,11 @@ export function checkGeneralProgram(program, options = {}) {
252
285
  if (options.publishedCatalog) {
253
286
  checkPortCaptureTypes(allocated, options.publishedCatalog, aliases, diagnostics);
254
287
  }
255
- const prefixByInstrument = new Map();
256
- if (options.publishedCatalog) {
257
- for (const inst of options.publishedCatalog.instruments) {
258
- if (inst.idPrefix) {
259
- prefixByInstrument.set(inst.id, inst.idPrefix);
260
- }
261
- }
262
- }
263
- for (const [name, decl] of templates) {
264
- const prefix = stringSlot(decl.body, "idPrefix", "id_prefix") ?? prefixFor(name);
265
- prefixByInstrument.set(name, prefix);
266
- }
267
288
  for (const candidate of allocated) {
268
289
  const prefix = stringSlot(candidate.body, "idPrefix", "id_prefix") ??
269
290
  prefixFor(candidate.name.name);
270
291
  prefixByInstrument.set(candidate.name.name, prefix);
271
292
  }
272
- const resolvePrefix = (target) => prefixByInstrument.get(target);
273
293
  const instruments = [];
274
294
  const ids = new Set();
275
295
  for (const candidate of allocated) {
@@ -282,7 +302,7 @@ export function checkGeneralProgram(program, options = {}) {
282
302
  report("HSX1022", candidate.name.span, `authored instrument ${candidate.name.name} collides with published catalog instrument`, "rename the authored instrument to avoid colliding with the published catalog");
283
303
  continue;
284
304
  }
285
- const expandedBody = expandComprehensions(candidate.body, diagnostics);
305
+ const expandedBody = expandComprehensions(candidate.body, diagnostics, new Map(), new Map(), resolvePrefix);
286
306
  const checked = checkInstrument(candidate.name, expandedBody, candidate.parties, candidate.ports, candidate.aliases, diagnostics, resolvePrefix);
287
307
  if (checked)
288
308
  instruments.push(checked);
@@ -838,8 +858,8 @@ function bindActionPorts(candidates, bindings, reached) {
838
858
  return { ...candidate, body: { ...candidate.body, entries }, ports };
839
859
  });
840
860
  }
841
- function constructedInstruments(rootName, body, diagnostics) {
842
- const expanded = expandComprehensions(body, diagnostics);
861
+ function constructedInstruments(rootName, body, diagnostics, resolvePrefix) {
862
+ const expanded = expandComprehensions(body, diagnostics, new Map(), new Map(), resolvePrefix);
843
863
  const construction = entry(expanded, "instruments");
844
864
  const root = {
845
865
  body: {
@@ -984,13 +1004,13 @@ function checkConstants(decls, aliases, diagnostics) {
984
1004
  validateMoneyLiteral(decl, diagnostics);
985
1005
  }
986
1006
  }
987
- function resolveConstants(decls) {
1007
+ function resolveConstants(decls, resolvePrefix) {
988
1008
  const constants = new Map(decls
989
1009
  .filter((decl) => decl.kind === "const")
990
1010
  .map((decl) => [decl.name.name, decl.value]));
991
1011
  for (let pass = 0; pass < constants.size; pass += 1) {
992
1012
  for (const [name, value] of constants) {
993
- constants.set(name, substituteExpr(value, constants));
1013
+ constants.set(name, substituteExpr(value, constants, resolvePrefix));
994
1014
  }
995
1015
  }
996
1016
  return constants;
@@ -1010,7 +1030,7 @@ function validateMoneyLiteral(decl, diagnostics) {
1010
1030
  span: decl.value.span,
1011
1031
  });
1012
1032
  }
1013
- function instantiate(template, application, diagnostics, instrumentName, ports, parties, aliases, constants, boundPorts) {
1033
+ function instantiate(template, application, diagnostics, instrumentName, ports, parties, aliases, constants, boundPorts, resolvePrefix) {
1014
1034
  const loweredPortShapes = new Map();
1015
1035
  const portDependencies = new Map();
1016
1036
  const partyKeyedParameters = partyKeyedBlockParameters(template, aliases);
@@ -1100,7 +1120,7 @@ function instantiate(template, application, diagnostics, instrumentName, ports,
1100
1120
  });
1101
1121
  continue;
1102
1122
  }
1103
- const expectedExpr = substituteExpr(optionalInner(parameter.type) ?? parameter.type, values);
1123
+ const expectedExpr = substituteExpr(optionalInner(parameter.type) ?? parameter.type, values, resolvePrefix);
1104
1124
  const expected = typeOf(expectedExpr, aliases);
1105
1125
  const actual = typeOf(value, aliases);
1106
1126
  const nonPartyKeys = partyKeyedParameters.get(parameter.name.name);
@@ -1152,7 +1172,7 @@ function instantiate(template, application, diagnostics, instrumentName, ports,
1152
1172
  else if (expected.kind !== "party" && value.kind === "ident") {
1153
1173
  fieldBindings.set(parameter.name.name, {
1154
1174
  name: value,
1155
- type: substituteExpr(optionalInner(parameter.type) ?? parameter.type, values),
1175
+ type: substituteExpr(optionalInner(parameter.type) ?? parameter.type, values, resolvePrefix),
1156
1176
  });
1157
1177
  values.set(parameter.name.name, value);
1158
1178
  }
@@ -1165,11 +1185,11 @@ function instantiate(template, application, diagnostics, instrumentName, ports,
1165
1185
  const port = ports.get(value.name.name);
1166
1186
  if (port)
1167
1187
  boundPorts.set(parameter.name.name, port);
1168
- bindPortCompileValues(values, parameter.name.name, value, ports, diagnostics, aliases, loweredPortShapes, portDependencies);
1188
+ bindPortCompileValues(values, parameter.name.name, value, ports, diagnostics, aliases, loweredPortShapes, portDependencies, resolvePrefix);
1169
1189
  }
1170
1190
  }
1171
1191
  checkClaimAmountPortField(resolvedArgs, ports, aliases, diagnostics);
1172
- const selected = selectCompileTimeRows(template.body, values, diagnostics, portDependencies);
1192
+ const selected = selectCompileTimeRows(template.body, values, diagnostics, portDependencies, [], resolvePrefix);
1173
1193
  const dependencyDiagnosticStart = diagnostics.length;
1174
1194
  for (const [name, parameter] of missingOptional) {
1175
1195
  if (!referencesIdentifier(selected, name, values))
@@ -1187,7 +1207,7 @@ function instantiate(template, application, diagnostics, instrumentName, ports,
1187
1207
  }
1188
1208
  if (diagnostics.length > dependencyDiagnosticStart)
1189
1209
  return undefined;
1190
- const body = expandComprehensions(selected, diagnostics, values, portDependencies);
1210
+ const body = expandComprehensions(selected, diagnostics, values, portDependencies, resolvePrefix);
1191
1211
  const binding = {
1192
1212
  key: { kind: "ident", name: "template_binding", span: application.span },
1193
1213
  qualifiers: [],
@@ -1426,7 +1446,7 @@ function shapeTypeText(expr) {
1426
1446
  }
1427
1447
  return nameText(expr);
1428
1448
  }
1429
- function lowerPortShapeField(port, row, aliases, diagnostics) {
1449
+ function lowerPortShapeField(port, row, aliases, diagnostics, resolvePrefix) {
1430
1450
  const type = typeOf(row.value, aliases);
1431
1451
  if (![
1432
1452
  "text",
@@ -1464,7 +1484,7 @@ function lowerPortShapeField(port, row, aliases, diagnostics) {
1464
1484
  extras.max_length = 180;
1465
1485
  extras.min_length = 1;
1466
1486
  if (type.target)
1467
- extras.pattern = `^${prefixFor(type.target)}_(sandbox|live)_[a-z0-9]{8,64}$`;
1487
+ extras.pattern = `^${resolvePrefix?.(type.target) ?? prefixFor(type.target)}_(sandbox|live)_[a-z0-9]{8,64}$`;
1468
1488
  }
1469
1489
  else if (type.kind === "account") {
1470
1490
  extras.pattern = ACCOUNT_PATTERN;
@@ -1522,7 +1542,7 @@ function lowerPortShapeField(port, row, aliases, diagnostics) {
1522
1542
  },
1523
1543
  };
1524
1544
  }
1525
- function lowerPortShape(port, rawShape, aliases, diagnostics) {
1545
+ function lowerPortShape(port, rawShape, aliases, diagnostics, resolvePrefix) {
1526
1546
  if (rawShape.kind !== "block") {
1527
1547
  diagnostics.push({
1528
1548
  code: "HSX1025",
@@ -1547,7 +1567,7 @@ function lowerPortShape(port, rawShape, aliases, diagnostics) {
1547
1567
  continue;
1548
1568
  }
1549
1569
  names.add(row.key.name);
1550
- const lowered = lowerPortShapeField(port, row, aliases, diagnostics);
1570
+ const lowered = lowerPortShapeField(port, row, aliases, diagnostics, resolvePrefix);
1551
1571
  if (lowered)
1552
1572
  entries.push(lowered);
1553
1573
  }
@@ -1631,7 +1651,7 @@ function checkClaimAmountPortField(resolvedArgs, ports, aliases, diagnostics) {
1631
1651
  }
1632
1652
  }
1633
1653
  }
1634
- function bindPortCompileValues(values, binding, value, ports, diagnostics, aliases = new Map(), loweredPortShapes = new Map(), dependencies = new Map()) {
1654
+ function bindPortCompileValues(values, binding, value, ports, diagnostics, aliases = new Map(), loweredPortShapes = new Map(), dependencies = new Map(), resolvePrefix) {
1635
1655
  const port = ports.get(value.name.name);
1636
1656
  if (!port) {
1637
1657
  diagnostics.push({
@@ -1655,7 +1675,7 @@ function bindPortCompileValues(values, binding, value, ports, diagnostics, alias
1655
1675
  span: port.body.span,
1656
1676
  };
1657
1677
  const shapeDiagnostics = [];
1658
- shape = lowerPortShape(port, rawShape, aliases, shapeDiagnostics);
1678
+ shape = lowerPortShape(port, rawShape, aliases, shapeDiagnostics, resolvePrefix);
1659
1679
  for (const diagnostic of shapeDiagnostics) {
1660
1680
  if (!diagnostics.some((prior) => prior.code === diagnostic.code &&
1661
1681
  prior.span === diagnostic.span &&
@@ -1679,7 +1699,7 @@ function bindPortCompileValues(values, binding, value, ports, diagnostics, alias
1679
1699
  if (value.deadline)
1680
1700
  values.set(`${binding}_deadline`, value.deadline);
1681
1701
  }
1682
- function substituteExpr(expr, values) {
1702
+ function substituteExpr(expr, values, resolvePrefix) {
1683
1703
  if (expr.kind === "ident") {
1684
1704
  return values.get(expr.name) ?? substituteName(expr, values);
1685
1705
  }
@@ -1717,7 +1737,7 @@ function substituteExpr(expr, values) {
1717
1737
  ? {
1718
1738
  iteration: {
1719
1739
  ...entry.iteration,
1720
- bound: substituteExpr(entry.iteration.bound, values),
1740
+ bound: substituteExpr(entry.iteration.bound, values, resolvePrefix),
1721
1741
  },
1722
1742
  }
1723
1743
  : {}),
@@ -1734,7 +1754,7 @@ function substituteExpr(expr, values) {
1734
1754
  ? nameFromValue(qualifier, value)
1735
1755
  : substituteName(qualifier, nestedValues);
1736
1756
  }),
1737
- value: substituteExpr(entry.value, nestedValues),
1757
+ value: substituteExpr(entry.value, nestedValues, resolvePrefix),
1738
1758
  };
1739
1759
  }),
1740
1760
  };
@@ -1742,20 +1762,20 @@ function substituteExpr(expr, values) {
1742
1762
  if (expr.kind === "list") {
1743
1763
  return {
1744
1764
  ...expr,
1745
- items: expr.items.map((item) => substituteExpr(item, values)),
1765
+ items: expr.items.map((item) => substituteExpr(item, values, resolvePrefix)),
1746
1766
  };
1747
1767
  }
1748
1768
  if (expr.kind === "type_apply") {
1749
1769
  return {
1750
1770
  ...expr,
1751
- args: expr.args.map((arg) => substituteExpr(arg, values)),
1771
+ args: expr.args.map((arg) => substituteExpr(arg, values, resolvePrefix)),
1752
1772
  };
1753
1773
  }
1754
1774
  if (expr.kind === "call") {
1755
1775
  const hasUnbound = expr.args.some((arg) => hasUnboundCompileIdentifier(arg, values));
1756
1776
  const substituted = {
1757
1777
  ...expr,
1758
- args: expr.args.map((arg) => substituteCompileArgument(arg, values)),
1778
+ args: expr.args.map((arg) => substituteCompileArgument(arg, values, resolvePrefix)),
1759
1779
  };
1760
1780
  const structural = new Set([
1761
1781
  "at",
@@ -1764,30 +1784,35 @@ function substituteExpr(expr, values) {
1764
1784
  "keys_except",
1765
1785
  "kind",
1766
1786
  "len",
1787
+ "prefix",
1767
1788
  "values",
1768
1789
  ]).has(expr.callee.name);
1769
1790
  return hasUnbound && !structural
1770
1791
  ? substituted
1771
- : (evaluateCompileTimeCall(substituted, !hasUnbound) ?? substituted);
1792
+ : (evaluateCompileTimeCall(substituted, !hasUnbound, resolvePrefix) ??
1793
+ substituted);
1772
1794
  }
1773
1795
  if (expr.kind === "apply") {
1774
1796
  return {
1775
1797
  ...expr,
1776
- args: expr.args.map((arg) => substituteExpr(arg, values)),
1777
- typeArgs: expr.typeArgs.map((arg) => substituteExpr(arg, values)),
1798
+ args: expr.args.map((arg) => substituteExpr(arg, values, resolvePrefix)),
1799
+ typeArgs: expr.typeArgs.map((arg) => substituteExpr(arg, values, resolvePrefix)),
1778
1800
  };
1779
1801
  }
1780
1802
  if (expr.kind === "binding") {
1781
- return { ...expr, type: substituteExpr(expr.type, values) };
1803
+ return { ...expr, type: substituteExpr(expr.type, values, resolvePrefix) };
1782
1804
  }
1783
1805
  if (expr.kind === "decided_amount") {
1784
- return { ...expr, body: substituteExpr(expr.body, values) };
1806
+ return {
1807
+ ...expr,
1808
+ body: substituteExpr(expr.body, values, resolvePrefix),
1809
+ };
1785
1810
  }
1786
1811
  return expr;
1787
1812
  }
1788
- function substituteCompileArgument(expr, values) {
1813
+ function substituteCompileArgument(expr, values, resolvePrefix) {
1789
1814
  if (expr.kind !== "ident")
1790
- return substituteExpr(expr, values);
1815
+ return substituteExpr(expr, values, resolvePrefix);
1791
1816
  const value = values.get(expr.name);
1792
1817
  if (!value)
1793
1818
  return expr;
@@ -1920,7 +1945,7 @@ function expressionPorts(value, dependencies) {
1920
1945
  }
1921
1946
  return [...found];
1922
1947
  }
1923
- function selectCompileTimeRows(block, values, diagnostics, dependencies = new Map(), controls = []) {
1948
+ function selectCompileTimeRows(block, values, diagnostics, dependencies = new Map(), controls = [], resolvePrefix) {
1924
1949
  const entries = [];
1925
1950
  const locals = new Map(values);
1926
1951
  const localDependencies = new Map(dependencies);
@@ -1929,13 +1954,13 @@ function selectCompileTimeRows(block, values, diagnostics, dependencies = new Ma
1929
1954
  const binding = row.qualifiers[0];
1930
1955
  if (binding) {
1931
1956
  localDependencies.set(binding.name, expressionPorts(row.value, localDependencies));
1932
- locals.set(binding.name, substituteExpr(row.value, locals));
1957
+ locals.set(binding.name, substituteExpr(row.value, locals, resolvePrefix));
1933
1958
  }
1934
1959
  entries.push(row);
1935
1960
  continue;
1936
1961
  }
1937
1962
  if (row.key.name === "unsupported") {
1938
- const value = substituteExpr(row.value, locals);
1963
+ const value = substituteExpr(row.value, locals, resolvePrefix);
1939
1964
  if (value.kind !== "block") {
1940
1965
  diagnostics.push({
1941
1966
  code: "HSX1405",
@@ -1965,7 +1990,7 @@ function selectCompileTimeRows(block, values, diagnostics, dependencies = new Ma
1965
1990
  row.key.name !== "when_not" &&
1966
1991
  row.key.name !== "when_eq") {
1967
1992
  const value = row.value.kind === "block" && !row.iteration
1968
- ? selectCompileTimeRows(row.value, locals, diagnostics, localDependencies, controls)
1993
+ ? selectCompileTimeRows(row.value, locals, diagnostics, localDependencies, controls, resolvePrefix)
1969
1994
  : row.value;
1970
1995
  const conditionPorts = row.key.name === "action"
1971
1996
  ? [
@@ -2022,7 +2047,7 @@ function selectCompileTimeRows(block, values, diagnostics, dependencies = new Ma
2022
2047
  ...controls,
2023
2048
  ...(subject ? expressionPorts(subject, localDependencies) : []),
2024
2049
  ]),
2025
- ]).entries);
2050
+ ], resolvePrefix).entries);
2026
2051
  }
2027
2052
  }
2028
2053
  return { ...block, entries };
@@ -2042,7 +2067,7 @@ function compileTimeTruthy(value) {
2042
2067
  return value.entries.length > 0;
2043
2068
  return true;
2044
2069
  }
2045
- function evaluateCompileTimeCall(expr, resolveMissing = true) {
2070
+ function evaluateCompileTimeCall(expr, resolveMissing = true, resolvePrefix) {
2046
2071
  const [first, second, third, fourth] = expr.args;
2047
2072
  const number = (value) => {
2048
2073
  if (value?.kind === "percent")
@@ -2226,7 +2251,9 @@ function evaluateCompileTimeCall(expr, resolveMissing = true) {
2226
2251
  case "words":
2227
2252
  return first ? text(nameText(first).replaceAll("_", " ")) : undefined;
2228
2253
  case "prefix":
2229
- return first ? text(prefixFor(nameText(first))) : undefined;
2254
+ return first
2255
+ ? text(resolvePrefix?.(nameText(first)) ?? prefixFor(nameText(first)))
2256
+ : undefined;
2230
2257
  case "owner":
2231
2258
  return first?.kind === "settlement_ref"
2232
2259
  ? text(first.owner.name)
@@ -2293,7 +2320,7 @@ function nameText(value) {
2293
2320
  return `${value.owner.name}.${value.member.name}`;
2294
2321
  return "invalid_compile_time_name";
2295
2322
  }
2296
- function expandComprehensions(block, diagnostics, values = new Map(), dependencies = new Map()) {
2323
+ function expandComprehensions(block, diagnostics, values = new Map(), dependencies = new Map(), resolvePrefix) {
2297
2324
  const budget = { used: 0 };
2298
2325
  const expand = (current, inherited = new Map(), inheritedDependencies = dependencies) => {
2299
2326
  const entries = [];
@@ -2313,7 +2340,7 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
2313
2340
  continue;
2314
2341
  }
2315
2342
  localDependencies.set(binding.name, expressionPorts(row.value, localDependencies));
2316
- locals.set(binding.name, substituteExpr(row.value, locals));
2343
+ locals.set(binding.name, substituteExpr(row.value, locals, resolvePrefix));
2317
2344
  continue;
2318
2345
  }
2319
2346
  if (row.key.name === "when" ||
@@ -2348,7 +2375,7 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
2348
2375
  ? nameFromValue(qualifier, value)
2349
2376
  : substituteName(qualifier, locals);
2350
2377
  }),
2351
- value: substituteExpr(row.value, locals),
2378
+ value: substituteExpr(row.value, locals, resolvePrefix),
2352
2379
  };
2353
2380
  entries.push({
2354
2381
  ...substituted,
@@ -2360,8 +2387,8 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
2360
2387
  }
2361
2388
  if (row.value.kind !== "block")
2362
2389
  continue;
2363
- const bound = substituteExpr(row.iteration.bound, locals);
2364
- const values = finiteIterationValues(bound);
2390
+ const bound = substituteExpr(row.iteration.bound, locals, resolvePrefix);
2391
+ const values = finiteIterationValues(bound, resolvePrefix);
2365
2392
  if (!values) {
2366
2393
  diagnostics.push({
2367
2394
  code: "HSX1403",
@@ -2388,7 +2415,7 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
2388
2415
  ...locals,
2389
2416
  [row.iteration.binding.name, value],
2390
2417
  ]);
2391
- const body = selectCompileTimeRows(row.value, substitutions, diagnostics, localDependencies, row.conditionPorts);
2418
+ const body = selectCompileTimeRows(row.value, substitutions, diagnostics, localDependencies, row.conditionPorts, resolvePrefix);
2392
2419
  entries.push(...expand(body, substitutions, localDependencies).entries);
2393
2420
  }
2394
2421
  }
@@ -2396,10 +2423,12 @@ function expandComprehensions(block, diagnostics, values = new Map(), dependenci
2396
2423
  };
2397
2424
  return expand(block, values);
2398
2425
  }
2399
- function finiteIterationValues(bound) {
2426
+ function finiteIterationValues(bound, resolvePrefix) {
2400
2427
  if (bound.kind === "call") {
2401
- const evaluated = evaluateCompileTimeCall(bound);
2402
- return evaluated ? finiteIterationValues(evaluated) : undefined;
2428
+ const evaluated = evaluateCompileTimeCall(bound, true, resolvePrefix);
2429
+ return evaluated
2430
+ ? finiteIterationValues(evaluated, resolvePrefix)
2431
+ : undefined;
2403
2432
  }
2404
2433
  if (bound.kind === "list")
2405
2434
  return bound.items;
@@ -2609,7 +2638,7 @@ function checkInstrument(name, body, parties, ports, aliases, diagnostics, resol
2609
2638
  if (!actionName || candidate.value.kind !== "block")
2610
2639
  return [];
2611
2640
  return [
2612
- checkAction(actionName, candidate.value, fields, name.name, endpointFields, holdDestinations, diagnostics),
2641
+ checkAction(actionName, candidate.value, fields, name.name, endpointFields, holdDestinations, diagnostics, resolvePrefix),
2613
2642
  ];
2614
2643
  })
2615
2644
  .map((action) => {
@@ -2888,7 +2917,7 @@ function lowerField(row, aliases, diagnostics, resolvePrefix) {
2888
2917
  extra = blockToObject({
2889
2918
  ...row.value,
2890
2919
  entries: row.value.entries.filter((item) => !["type", "description", "desc", "optional", "allow_zero"].includes(item.key.name)),
2891
- }, new Map(), diagnostics);
2920
+ }, new Map(), diagnostics, false, resolvePrefix);
2892
2921
  }
2893
2922
  const type = typeOf(typeExpr, aliases);
2894
2923
  // Diagnostics point at the field's own type expression, not at an alias
@@ -3030,7 +3059,7 @@ function checkLifecycle(instrument, block, actions, diagnostics) {
3030
3059
  }
3031
3060
  return { parked, value: { initial, states, transitions } };
3032
3061
  }
3033
- function checkAction(name, body, fields, instrumentId, endpointFields, holdDestinations, diagnostics) {
3062
+ function checkAction(name, body, fields, instrumentId, endpointFields, holdDestinations, diagnostics, resolvePrefix) {
3034
3063
  const slots = {};
3035
3064
  let omitsPublicAction = false;
3036
3065
  const fieldNames = new Set(fields.map((field) => field.name));
@@ -3076,7 +3105,7 @@ function checkAction(name, body, fields, instrumentId, endpointFields, holdDesti
3076
3105
  });
3077
3106
  }
3078
3107
  }
3079
- const rawValue = exprToJsonForUdlSlot(row.value, definition.target, diagnostics);
3108
+ const rawValue = exprToJsonForUdlSlot(row.value, definition.target, diagnostics, resolvePrefix);
3080
3109
  const moveOffset = Array.isArray(slots.moves) ? slots.moves.length : 0;
3081
3110
  const value = definition.target === "moves"
3082
3111
  ? normalizeMoves(rawValue, name.name, instrumentId, fields, endpointFields, holdDestinations, diagnostics, row.span, moveOffset)
@@ -3788,12 +3817,12 @@ function stringSlot(block, ...names) {
3788
3817
  return found.name;
3789
3818
  return undefined;
3790
3819
  }
3791
- function blockToObject(block, substitutions, diagnostics, literalKeys = false) {
3820
+ function blockToObject(block, substitutions, diagnostics, literalKeys = false, resolvePrefix) {
3792
3821
  if (!block)
3793
3822
  return {};
3794
3823
  const result = {};
3795
3824
  for (const row of block.entries) {
3796
- const value = substituteExpr(row.value, substitutions);
3825
+ const value = substituteExpr(row.value, substitutions, resolvePrefix);
3797
3826
  if (twoArgumentMoneyCall(value)) {
3798
3827
  diagnostics.push({
3799
3828
  code: "HSX1104",
@@ -3805,11 +3834,11 @@ function blockToObject(block, substitutions, diagnostics, literalKeys = false) {
3805
3834
  continue;
3806
3835
  }
3807
3836
  result[literalKeys || row.key.quoted ? row.key.name : camel(row.key.name)] =
3808
- exprToJson(value, diagnostics, literalKeys);
3837
+ exprToJson(value, diagnostics, literalKeys, resolvePrefix);
3809
3838
  }
3810
3839
  return result;
3811
3840
  }
3812
- function exprToJsonForUdlSlot(expr, slot, diagnostics) {
3841
+ function exprToJsonForUdlSlot(expr, slot, diagnostics, resolvePrefix) {
3813
3842
  if (twoArgumentMoneyCall(expr)) {
3814
3843
  diagnostics.push({
3815
3844
  code: "HSX1104",
@@ -3822,16 +3851,16 @@ function exprToJsonForUdlSlot(expr, slot, diagnostics) {
3822
3851
  if (slot === "callerParkedStates" && expr.kind === "block") {
3823
3852
  return Object.fromEntries(expr.entries.map((row) => [
3824
3853
  row.key.name,
3825
- exprToJson(row.value, diagnostics),
3854
+ exprToJson(row.value, diagnostics, false, resolvePrefix),
3826
3855
  ]));
3827
3856
  }
3828
3857
  // Declaration maps retain their authored keys; field-spelling aliases still normalize.
3829
3858
  const literalKeys = udlClauseVocabulary.some((clause) => clause.target === slot &&
3830
3859
  "literalKeys" in clause &&
3831
3860
  clause.literalKeys === true);
3832
- return exprToJson(expr, diagnostics, literalKeys);
3861
+ return exprToJson(expr, diagnostics, literalKeys, resolvePrefix);
3833
3862
  }
3834
- function exprToJson(expr, diagnostics, literalKeys = false) {
3863
+ function exprToJson(expr, diagnostics, literalKeys = false, resolvePrefix) {
3835
3864
  switch (expr.kind) {
3836
3865
  case "boolean":
3837
3866
  return expr.value;
@@ -3873,17 +3902,17 @@ function exprToJson(expr, diagnostics, literalKeys = false) {
3873
3902
  case "settlement_ref":
3874
3903
  return `${expr.owner.name}.${expr.member.name}`;
3875
3904
  case "list":
3876
- return expr.items.map((item) => exprToJson(item, diagnostics, literalKeys));
3905
+ return expr.items.map((item) => exprToJson(item, diagnostics, literalKeys, resolvePrefix));
3877
3906
  case "block":
3878
- return blockToObject(expr, new Map(), diagnostics, literalKeys);
3907
+ return blockToObject(expr, new Map(), diagnostics, literalKeys, resolvePrefix);
3879
3908
  case "call":
3880
- return `${expr.callee.name}(${expr.args.map((arg) => String(exprToJson(arg, diagnostics))).join(",")})`;
3909
+ return `${expr.callee.name}(${expr.args.map((arg) => String(exprToJson(arg, diagnostics, false, resolvePrefix))).join(",")})`;
3881
3910
  case "type_apply":
3882
- return `${expr.callee.name}<${expr.args.map((arg) => String(exprToJson(arg, diagnostics))).join(",")}>`;
3911
+ return `${expr.callee.name}<${expr.args.map((arg) => String(exprToJson(arg, diagnostics, false, resolvePrefix))).join(",")}>`;
3883
3912
  case "binding":
3884
- return exprToJson(expr.type, diagnostics);
3913
+ return exprToJson(expr.type, diagnostics, false, resolvePrefix);
3885
3914
  case "decided_amount":
3886
- return blockToObject(expr.body, new Map(), diagnostics);
3915
+ return blockToObject(expr.body, new Map(), diagnostics, false, resolvePrefix);
3887
3916
  case "port_ref":
3888
3917
  return expr.name.name;
3889
3918
  case "apply":