@hyperscale0/hsx 1.0.124 → 1.0.128

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,8 +21,8 @@ Start with the [first program and rental guide](docs/README.md). Save the
21
21
  It holds 1,000 SAR, charges a one-time 50 SAR late fee, and returns the remainder.
22
22
  The guide explains the fixed business binding and the host needed to execute it.
23
23
 
24
- `npx hsx headers` prints every std instrument and its tunables from the bundled
25
- headers. The [samples](examples/) include both complete action paths and
24
+ `npx hsx headers --json` prints every std instrument and its tunables from the
25
+ bundled headers as JSON. The [samples](examples/) include both complete action paths and
26
26
  compositions that still need child-record actions or adapter bindings. A compile
27
27
  pass does not prove that a flow is publicly executable or funded.
28
28
 
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAgBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAqTD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAgnHf"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AAOA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAgBL,KAAK,WAAW,EAQjB,MAAM,kBAAkB,CAAC;AAU1B,OAAO,EAIL,KAAK,UAAU,EAKf,KAAK,IAAI,EACV,MAAM,UAAU,CAAC;AAClB,OAAO,EAA0B,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;CACpC;AACD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AACD,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AACD,MAAM,WAAW,cAAc;IAC7B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAClE;AACD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,SAAS,CAAC,EAAE;QACV,QAAQ,EAAE,WAAW,CAAC;QACtB,YAAY,EAAE,eAAe,CAAC;QAC9B,SAAS,EAAE,qBAAqB,EAAE,CAAC;KACpC,CAAC;CACH;AAqTD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAqrHf"}
@@ -349,10 +349,6 @@ export function compile(source, options = {}) {
349
349
  for (const decl of program.decls)
350
350
  if (decl.kind === "instrument")
351
351
  templates.set(decl.name, decl);
352
- for (const decl of program.decls) {
353
- if (decl.kind === "instrument" && decl.parameters.length)
354
- fail(decl, "This compiler cannot instantiate a parameterized instrument declared inside a program.", "For this version, specialize the instrument with fixed bindings and remove its parameters. Custom reusable headers require a host-supplied library.");
355
- }
356
352
  const document = {
357
353
  udl: UDL_FORMAT_VERSION,
358
354
  version: 1,
@@ -600,7 +596,11 @@ export function compile(source, options = {}) {
600
596
  const fallback = param.value.kind === "default" ? param.value.value : undefined;
601
597
  const typeName = type.kind === "type" || type.kind === "call" ? type.name : text(type);
602
598
  const partyParameter = attachmentInfo && typeName === "party";
599
+ const optional = type.kind === "type" && type.optional;
600
+ // An optional party stays unset unless the attachment binds it, so a
601
+ // program party that shares its name does not switch it on.
603
602
  const byName = partyParameter &&
603
+ !optional &&
604
604
  (subjectPartyRoles.includes(param.key) ||
605
605
  document.parties[param.key])
606
606
  ? { kind: "name", value: param.key, span: origin }
@@ -612,7 +612,7 @@ export function compile(source, options = {}) {
612
612
  source: declarationSources.get(decl) ?? "program",
613
613
  });
614
614
  if (!actual) {
615
- if (type.kind === "type" && type.optional)
615
+ if (optional)
616
616
  continue;
617
617
  parameterErrors.push({
618
618
  span: origin,
@@ -678,7 +678,9 @@ export function compile(source, options = {}) {
678
678
  const suppliedValue = supplied.get(key);
679
679
  const requirements = decl.body.entries
680
680
  .filter((entry) => entry.key.startsWith("action "))
681
- .flatMap((entry) => asBlock(entries(asBlock(entry.value)).get("subject")).entries);
681
+ .flatMap((entry) =>
682
+ // Read subject rows directly: entries() refuses repeated when branches.
683
+ asBlock(asBlock(entry.value).entries.find((row) => row.key === "subject")?.value).entries);
682
684
  const moneyFields = [
683
685
  ...new Set(requirements
684
686
  .filter((entry) => {
@@ -1653,10 +1655,10 @@ export function compile(source, options = {}) {
1653
1655
  };
1654
1656
  }
1655
1657
  }
1656
- const fromBinding = slots.get("from");
1657
- if (fromBinding?.kind === "name" &&
1658
- fromBinding.value.includes(".") &&
1659
- slots.has("moves")) {
1658
+ const from = slots.get("from");
1659
+ // A policy member may sit in a state list such as `[funded, dispute.refund_after]`.
1660
+ const fromBinding = (from?.kind === "list" ? from.items : [from]).find((item) => item?.kind === "name" && item.value.includes("."));
1661
+ if (fromBinding?.kind === "name" && slots.has("moves")) {
1660
1662
  const [parameter, member] = fromBinding.value.split(".");
1661
1663
  const suppliedPolicy = supplied.get(parameter);
1662
1664
  if (suppliedPolicy?.kind === "block") {
@@ -1693,6 +1695,7 @@ export function compile(source, options = {}) {
1693
1695
  ...(actionSubject ? { subject: actionSubject } : {}),
1694
1696
  };
1695
1697
  authored.set(a, row.value);
1698
+ const actionFeeCalculations = [];
1696
1699
  for (const binding of boundaryBindings)
1697
1700
  if (!a.subject?.adapters.find((entry) => entry.binding === binding)
1698
1701
  ?.snapshot)
@@ -1920,18 +1923,31 @@ export function compile(source, options = {}) {
1920
1923
  if (rate.kind !== "percent")
1921
1924
  fail(rate, "fee rate needs a percentage", "write a percentage such as 1%");
1922
1925
  const prefix = `${name}_${key}`;
1923
- const derived = (suffix, calculation) => {
1926
+ // Instrument calculations run at create, before any action
1927
+ // input exists, so a fee on an input amount runs with its action.
1928
+ const perAction = "field" in transfer.amount &&
1929
+ transfer.amount.field.startsWith("input.");
1930
+ const derived = (suffix, label, calculation) => {
1924
1931
  const target = prefix + "_" + suffix;
1925
1932
  if (fields.some((f) => f.name === target))
1926
1933
  fail(move, `generated fee field ${target} conflicts with a field`, "rename the authored field or move key");
1927
- fields.push({ name: target, type: "money" });
1928
- calculations.push({
1934
+ fields.push({
1935
+ name: target,
1936
+ type: "money",
1937
+ label,
1938
+ ...(perAction ? { optional: true } : {}),
1939
+ });
1940
+ (perAction ? actionFeeCalculations : calculations).push({
1929
1941
  ...calculation,
1930
1942
  target,
1931
1943
  });
1932
1944
  return { field: `self.${target}` };
1933
1945
  };
1934
- const gross = derived("feeGross", {
1946
+ // Labels come from the source keys: seller or buyer, cap, tax.
1947
+ const feeLabel = `${paidBy === "seller" ? "Seller" : "Buyer"} fee`;
1948
+ const gross = derived("feeGross", quoted.kind === "capped"
1949
+ ? `${feeLabel} before cap`
1950
+ : feeLabel, {
1935
1951
  op: "rate",
1936
1952
  base: transfer.amount,
1937
1953
  bps: { literal: literal(rate) },
@@ -1941,7 +1957,7 @@ export function compile(source, options = {}) {
1941
1957
  if (quoted.kind === "capped") {
1942
1958
  if (quoted.cap.kind !== "money")
1943
1959
  fail(quoted.cap, "fee cap needs money", "write an amount such as 750 SAR");
1944
- charge = derived("fee", {
1960
+ charge = derived("fee", feeLabel, {
1945
1961
  op: "minimum",
1946
1962
  values: [gross, { literal: literal(quoted.cap) }],
1947
1963
  });
@@ -1950,7 +1966,7 @@ export function compile(source, options = {}) {
1950
1966
  if (tax && tax.kind !== "percent")
1951
1967
  fail(tax, "tax needs a percentage of the fee", "write tax: 15%");
1952
1968
  const vat = tax
1953
- ? derived("tax", {
1969
+ ? derived("tax", "Fee tax", {
1954
1970
  op: "rate",
1955
1971
  base: charge,
1956
1972
  bps: { literal: literal(tax) },
@@ -1959,7 +1975,7 @@ export function compile(source, options = {}) {
1959
1975
  : undefined;
1960
1976
  const charges = vat ? [charge, vat] : [charge];
1961
1977
  const net = paidBy === "seller"
1962
- ? derived("net", {
1978
+ ? derived("net", "Amount after fee", {
1963
1979
  op: "subtract",
1964
1980
  base: transfer.amount,
1965
1981
  subtract: charges,
@@ -2022,6 +2038,8 @@ export function compile(source, options = {}) {
2022
2038
  else
2023
2039
  a[key] = data(expr);
2024
2040
  }
2041
+ if (actionFeeCalculations.length)
2042
+ a.calculate = [...(a.calculate ?? []), ...actionFeeCalculations];
2025
2043
  if (attachmentInfo && !attachmentInfo.child) {
2026
2044
  if (attachmentInfo.exposed.has(name)) {
2027
2045
  if (automatic(a.actor))
@@ -2373,7 +2391,9 @@ export function compile(source, options = {}) {
2373
2391
  });
2374
2392
  };
2375
2393
  for (const decl of program.decls) {
2376
- if (decl.kind === "instrument") {
2394
+ // A parameterized program instrument is a template, like a header
2395
+ // instrument: each attachment instantiates it with its own bindings.
2396
+ if (decl.kind === "instrument" && !decl.parameters.length) {
2377
2397
  addInstrument(decl, decl.name, emptyBlock, decl.span);
2378
2398
  }
2379
2399
  if (decl.kind === "assignment") {
@@ -2628,7 +2648,9 @@ export function compile(source, options = {}) {
2628
2648
  const checkParty = (name, at, inst) => {
2629
2649
  const names = parties(inst);
2630
2650
  if (!names.includes(name))
2631
- fail(at, `\`${name}\` is not a declared party.${didYouMean(name, names)}`, `Declare it with \`party ${name}: business\`, or name a declared party (${names.join(", ")}).`);
2651
+ fail(at, `\`${name}\` is not a declared party.${didYouMean(name, names)}`, subjectPartyRoles.includes(name)
2652
+ ? `Declare a party parameter such as \`holder: party\` on the instrument, use it here, and bind \`holder: ${name}\` where you attach it.`
2653
+ : `Declare it with \`party ${name}: business\`, or name a declared party (${names.join(", ")}).`);
2632
2654
  };
2633
2655
  const checkInput = (name, at, label, input) => {
2634
2656
  const names = input.map((field) => field.name);
@@ -2681,9 +2703,39 @@ export function compile(source, options = {}) {
2681
2703
  inst,
2682
2704
  label: declarations.get(inst).name,
2683
2705
  });
2706
+ // A family member drops an optional reference field its binding left
2707
+ // unset (a borrower-funded plan has no `funds`), so it cannot match and
2708
+ // leaves the selection. The header must still declare the field.
2709
+ const matchable = (selection) => {
2710
+ const members = [selection.instrument].flat();
2711
+ const header = selection.family
2712
+ ? templates.get(`${selection.family.module}.${selection.family.exportPath}`)
2713
+ : undefined;
2714
+ if (!header ||
2715
+ !entries(asBlock(entries(header.body).get("fields"))).has(selection.reference))
2716
+ return members;
2717
+ return members.filter((id) => resolveField(document, byId.get(id), `self.${selection.reference}`));
2718
+ };
2719
+ // A count over no matchable member is zero.
2720
+ for (const inst of document.instruments)
2721
+ for (const action of Object.values(inst.actions))
2722
+ action.requires = action.requires.map((requirement) => requirement.kind === "aggregate" &&
2723
+ requirement.measure === "count" &&
2724
+ !matchable(requirement.selection).length
2725
+ ? {
2726
+ kind: "compare",
2727
+ left: { literal: 0 },
2728
+ operator: requirement.operator,
2729
+ right: requirement.value,
2730
+ }
2731
+ : requirement);
2684
2732
  const checkSelection = (selection, at, scope) => {
2685
2733
  walk(selection.anchor, "anchor", site(selection, "anchor", at), scope);
2686
2734
  walk(selection.where, "where", site(selection, "where", at), scope);
2735
+ const members = matchable(selection);
2736
+ if (members.length &&
2737
+ members.length < [selection.instrument].flat().length)
2738
+ selection.instrument = members;
2687
2739
  for (const id of [selection.instrument].flat()) {
2688
2740
  checkTarget(id, site(selection, "instrument", at));
2689
2741
  const target = scopeOf(byId.get(id));