@hyperscale0/hsx 4.0.1 → 4.1.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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # HSX 4.1.1
2
+
3
+ `examples/serviced.hsx` is a second worked programme: a financed sale with a late charge, payment reminders, an early payoff rebate and a write-off. The car financing spec compiles it from the file. No grammar or standard library change.
4
+
5
+ # HSX 4.1.0
6
+
7
+ Second-person approval is deleted from the grammar and the standard library. `financing.portfolio_limit` is a top-level instrument, a plan's `funds` hold is optional, and `insurance.cover` and `financing.credit_line` name their provider with `adapter: text` instead of a party. A car financing programme compiles from the standard library and is covered by its own spec.
8
+
1
9
  # HSX 4.0.1
2
10
 
3
11
  The compiler resolves list targets in field blocks and child record export paths, refuses duplicate child export path suffixes naming both candidates, and no longer restricts aggregate limit bounds at compile time; `<= self.<field>` bounds resolve at runtime. No grammar change.
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAcL,KAAK,WAAW,EAOjB,MAAM,kBAAkB,CAAC;AAG1B,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;CAC/C;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;AA2TD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAspFf"}
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/compile.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,WAAW,CAAC;AACvE,OAAO,EAcL,KAAK,WAAW,EAOjB,MAAM,kBAAkB,CAAC;AAG1B,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;CAC/C;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;AA2TD,wBAAgB,OAAO,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC3B,aAAa,CAs+Ef"}
@@ -368,8 +368,6 @@ export function compile(source, options = {}) {
368
368
  }
369
369
  }
370
370
  const origins = [];
371
- const materialApprovals = new Set();
372
- const implicitDecisions = new Map();
373
371
  const resolveFamily = (rawPath, expr, required = true) => {
374
372
  const parts = rawPath.split(".");
375
373
  if (parts.length < 2) {
@@ -419,7 +417,7 @@ export function compile(source, options = {}) {
419
417
  }
420
418
  return { module: moduleName, exportPath, revision };
421
419
  };
422
- const addInstrument = (decl, id, arguments_, origin, inherited = new Map(), inheritedApprovers = new Set(), inheritedEnums = new Map(), attachmentInfo, familyDeclaration) => {
420
+ const addInstrument = (decl, id, arguments_, origin, inherited = new Map(), inheritedEnums = new Map(), attachmentInfo, familyDeclaration) => {
423
421
  const resolveFamilyInstruments = (family, currentInstId, expr) => {
424
422
  const found = new Set();
425
423
  if (currentInstId && familyDeclaration) {
@@ -551,7 +549,6 @@ export function compile(source, options = {}) {
551
549
  if (type.kind === "call" && type.name === "enum")
552
550
  enums.set(parameter.key, type.args.map(text));
553
551
  }
554
- const approvers = new Set(inheritedApprovers);
555
552
  const supplied = new Map();
556
553
  for (const entry of arguments_.entries) {
557
554
  if (supplied.has(entry.key))
@@ -563,7 +560,7 @@ export function compile(source, options = {}) {
563
560
  const type = param.value.kind === "default" ? param.value.type : param.value;
564
561
  const fallback = param.value.kind === "default" ? param.value.value : undefined;
565
562
  const typeName = type.kind === "type" || type.kind === "call" ? type.name : text(type);
566
- const partyParameter = attachmentInfo && (typeName === "party" || typeName === "approval");
563
+ const partyParameter = attachmentInfo && typeName === "party";
567
564
  const byName = partyParameter &&
568
565
  (subjectPartyRoles.includes(param.key) ||
569
566
  document.parties[param.key])
@@ -671,7 +668,7 @@ export function compile(source, options = {}) {
671
668
  const type = t.kind === "type" || t.kind === "call" ? t.name : text(t);
672
669
  const v = (supplied.has(param.key) && !attachmentInfo) || type === "enum"
673
670
  ? actual
674
- : resolve(actual, new Set(), !!attachmentInfo && (type === "party" || type === "approval"));
671
+ : resolve(actual, new Set(), !!attachmentInfo && type === "party");
675
672
  environment.set(param.key, v);
676
673
  if (type === "enum" && t.kind === "call") {
677
674
  if (v.kind !== "name" || !t.args.some((a) => text(a) === v.value))
@@ -681,24 +678,18 @@ export function compile(source, options = {}) {
681
678
  if (v.kind !== "list")
682
679
  fail(v, `${param.key} needs a list`, "write [value, value]");
683
680
  }
684
- else if (type === "party" || type === "approval") {
681
+ else if (type === "party") {
685
682
  if (v.kind !== "name" || !isParty(v.value))
686
683
  failWithCode(actual, attachmentInfo ? "subject_party_unbound" : "HSX1001", `${param.key} needs a declared party`, "declare a party and use its name here");
687
684
  const party = document.parties[v.value];
688
- if ((type === "approval" && (party?.kind !== "staff" || !party.role)) ||
689
- (type === "party" &&
690
- (party?.kind === "staff" ||
691
- (attachmentInfo && party?.kind === "person"))))
692
- failWithCode(actual, "party_kind_mismatch", `${param.key} cannot bind ${v.value}`, type === "approval"
693
- ? "use a declared staff party with a role"
694
- : "use a subject role or declared business");
685
+ if ((party?.kind === "staff" && !party.role) ||
686
+ (attachmentInfo && party?.kind === "person"))
687
+ failWithCode(actual, "party_kind_mismatch", `${param.key} cannot bind ${v.value}`, "use a subject role, declared business, or staff with a role");
695
688
  resolvedParties.add(param.key);
696
689
  if (attachmentInfo)
697
690
  attachmentInfo.parties[param.key] = subjectPartyRoles.includes(v.value)
698
691
  ? { role: v.value }
699
692
  : { party: v.value };
700
- if (type === "approval")
701
- approvers.add(text(v));
702
693
  }
703
694
  else if (type === "ref") {
704
695
  const values = v.kind === "list" ? v.items : [v];
@@ -725,6 +716,14 @@ export function compile(source, options = {}) {
725
716
  fail(value, "duplicate reference", "list each object once");
726
717
  seen.add(value.value);
727
718
  }
719
+ // A many-reference tunable is a list even when one object is bound,
720
+ // so report datasets and other value positions never see a bare name.
721
+ if (t.kind === "type" && t.many && v.kind !== "list")
722
+ environment.set(param.key, {
723
+ kind: "list",
724
+ items: [v],
725
+ span: v.span,
726
+ });
728
727
  }
729
728
  else if (type === "fee" || type === "split" || type === "policy") {
730
729
  if (v.kind !== "block")
@@ -1009,6 +1008,12 @@ export function compile(source, options = {}) {
1009
1008
  fail(row, "reference needs a target", "write ref<object>");
1010
1009
  const [root, ...tail] = target.split(".");
1011
1010
  const resolved = environment.get(root);
1011
+ // An optional field pointing at an unsupplied optional tunable
1012
+ // has nothing to point at, so the instrument drops it.
1013
+ if (!resolved &&
1014
+ f.optional &&
1015
+ decl.parameters.some((parameter) => parameter.key === root))
1016
+ continue;
1012
1017
  const resolvedTargets = resolved?.kind === "list"
1013
1018
  ? resolved.items
1014
1019
  : resolved
@@ -1272,9 +1277,14 @@ export function compile(source, options = {}) {
1272
1277
  const adapterList = [];
1273
1278
  for (const entry of subjectBlock.entries) {
1274
1279
  if (entry.key === "adapter") {
1280
+ // A tunable names the adapter; anything else is the binding itself.
1281
+ const adapterName = (item) => item.kind === "name" &&
1282
+ decl.parameters.some((parameter) => parameter.key === item.value)
1283
+ ? text(resolve(item))
1284
+ : text(item);
1275
1285
  const bindingNames = entry.value.kind === "list"
1276
- ? entry.value.items.map(text)
1277
- : [text(entry.value)];
1286
+ ? entry.value.items.map(adapterName)
1287
+ : [adapterName(entry.value)];
1278
1288
  for (const bindingName of bindingNames) {
1279
1289
  const target = options.adapterRegistry?.[bindingName];
1280
1290
  if (target) {
@@ -1626,14 +1636,6 @@ export function compile(source, options = {}) {
1626
1636
  fail(move, `unknown transfer instruction ${op}`, "choose create, reserve, post, or void from internal_transfer");
1627
1637
  }
1628
1638
  }
1629
- else if (key === "approval") {
1630
- const approval = data(expr);
1631
- if (approval && approval.input === "material") {
1632
- approval.input = {};
1633
- materialApprovals.add(a);
1634
- }
1635
- a.approval = approval;
1636
- }
1637
1639
  else
1638
1640
  a[key] = data(expr);
1639
1641
  }
@@ -1671,26 +1673,6 @@ export function compile(source, options = {}) {
1671
1673
  checkSubjectPaths(a.invoke, row.span);
1672
1674
  currentAction = undefined;
1673
1675
  currentActionName = undefined;
1674
- for (const requirement of a.requires ?? []) {
1675
- if (requirement.kind !== "approval" ||
1676
- !approvers.has(requirement.party))
1677
- continue;
1678
- const action = requirement.action ?? name;
1679
- const key = `${id}_${action}_decision`;
1680
- const previous = implicitDecisions.get(key);
1681
- if (previous &&
1682
- (previous.party !== requirement.party ||
1683
- previous.protectedRequest !==
1684
- (requirement.protectedRequest ?? "self")))
1685
- fail({ span: origin }, `action ${action} has conflicting approval parties or protected requests`, "use a separate decision action for each party");
1686
- implicitDecisions.set(key, {
1687
- target: id,
1688
- action,
1689
- party: requirement.party,
1690
- protectedRequest: requirement.protectedRequest ?? "self",
1691
- origin,
1692
- });
1693
- }
1694
1676
  for (const requirement of a.subject?.requirements ?? []) {
1695
1677
  const entry = asBlock(subjectExpr).entries.find((entry) => entry.key === requirement.field.name) ??
1696
1678
  asBlock(subjectExpr).entries.find((entry) => entry.key === "adapter");
@@ -1739,7 +1721,7 @@ export function compile(source, options = {}) {
1739
1721
  addInstrument(child, `${id}_${key}`, emptyBlock, origin, new Map([
1740
1722
  ...environment,
1741
1723
  ["parent", { kind: "name", value: id, span: origin }],
1742
- ]), approvers, enums, attachmentInfo
1724
+ ]), enums, attachmentInfo
1743
1725
  ? { ...attachmentInfo, exposed: new Map() }
1744
1726
  : undefined, childFamily);
1745
1727
  }
@@ -1812,7 +1794,7 @@ export function compile(source, options = {}) {
1812
1794
  span: entry.value.span,
1813
1795
  };
1814
1796
  const templateFamily = resolveFamily(targetTemplate, entry, false);
1815
- addInstrument(template, instId, tunableBlock, entry.span, new Map(), new Set(), new Map(), {
1797
+ addInstrument(template, instId, tunableBlock, entry.span, new Map(), new Map(), {
1816
1798
  subjectKindId: decl.name,
1817
1799
  attachmentName,
1818
1800
  renames,
@@ -1901,7 +1883,7 @@ export function compile(source, options = {}) {
1901
1883
  if (!template)
1902
1884
  fail(decl, `unknown instrument ${decl.target}`, `add use ${decl.target.split(".")[0]} and choose a declared instrument`);
1903
1885
  const templateFamily = resolveFamily(decl.target, decl, false);
1904
- addInstrument(template, decl.name, decl.body, decl.span, new Map(), new Set(), new Map(), undefined, templateFamily ? { ...templateFamily } : undefined);
1886
+ addInstrument(template, decl.name, decl.body, decl.span, new Map(), new Map(), undefined, templateFamily ? { ...templateFamily } : undefined);
1905
1887
  }
1906
1888
  if (decl.kind === "object") {
1907
1889
  compileObject(decl);
@@ -2070,99 +2052,6 @@ export function compile(source, options = {}) {
2070
2052
  : field.target).some((id) => eliminatedStates.get(id)?.has(state)));
2071
2053
  }
2072
2054
  }
2073
- // Approval tunables instantiate the same library record as an explicit decision.
2074
- if (implicitDecisions.size) {
2075
- const content = (options.standardLibrary ?? bundledStandardLibrary).source("approvals");
2076
- const parsed = content ? parseProgram(content) : undefined;
2077
- const template = parsed?.program.decls.find((decl) => decl.kind === "instrument" && decl.name === "decision");
2078
- if (!template ||
2079
- template.kind !== "instrument" ||
2080
- parsed?.diagnostics.length)
2081
- fail(program, "implicit decisions need the approvals header", "provide the standard approvals header");
2082
- for (const [id, decision] of implicitDecisions) {
2083
- if (!document.instruments.find((inst) => inst.id === decision.target)
2084
- ?.actions[decision.action])
2085
- continue;
2086
- const existing = document.instruments.some((inst) => Object.values(inst.actions).some((action) => action.approval?.action === decision.action &&
2087
- action.approval.party === decision.party &&
2088
- inst.fields.some((field) => {
2089
- if (field.type !== "ref" ||
2090
- field.target !== decision.target ||
2091
- `self.${field.name}` !== action.approval?.target)
2092
- return false;
2093
- const [root, name, ...tail] = decision.protectedRequest.split(".");
2094
- const input = name && action.approval.input[name];
2095
- const request = root === "self"
2096
- ? [action.approval.target, name, ...tail]
2097
- .filter(Boolean)
2098
- .join(".")
2099
- : root === "input" && name
2100
- ? input && "field" in input
2101
- ? [input.field, ...tail].join(".")
2102
- : materialApprovals.has(action)
2103
- ? [`self.material_${name}`, ...tail].join(".")
2104
- : undefined
2105
- : undefined;
2106
- return (request !== undefined &&
2107
- action.approval.protectedRequest === request);
2108
- })));
2109
- if (existing)
2110
- continue;
2111
- if (document.instruments.some((inst) => inst.id === id))
2112
- fail({ span: decision.origin }, `implicit decision name ${id} is already used`, "rename the conflicting object");
2113
- const protectedRequest = decision.protectedRequest === "self"
2114
- ? "self.target"
2115
- : decision.protectedRequest.startsWith("self.")
2116
- ? `self.target.${decision.protectedRequest.slice(5)}`
2117
- : fail({ span: decision.origin }, "implicit approval needs a stored protected request", "declare an explicit decision for an input-based protected request");
2118
- addInstrument(template, id, {
2119
- kind: "block",
2120
- span: decision.origin,
2121
- entries: Object.entries({
2122
- for: decision.target,
2123
- approved_by: decision.party,
2124
- action: decision.action,
2125
- protected_request: protectedRequest,
2126
- }).map(([key, value]) => ({
2127
- key,
2128
- value: {
2129
- kind: key === "action" || key === "protected_request"
2130
- ? "text"
2131
- : "name",
2132
- value,
2133
- span: decision.origin,
2134
- },
2135
- span: decision.origin,
2136
- })),
2137
- }, decision.origin, new Map(), new Set(), new Map(), document.instruments.find((instrument) => instrument.id === decision.target)?.subject
2138
- ? {
2139
- subjectKindId: document.instruments.find((instrument) => instrument.id === decision.target).subject,
2140
- attachmentName: id,
2141
- parties: {},
2142
- renames: new Map(),
2143
- exposed: new Map(),
2144
- }
2145
- : undefined);
2146
- }
2147
- }
2148
- // Material approval fields are inferred from the selected action's typed input.
2149
- for (const inst of document.instruments)
2150
- for (const action of Object.values(inst.actions)) {
2151
- if (!materialApprovals.has(action) || !action.approval)
2152
- continue;
2153
- const targetField = inst.fields.find((f) => `self.${f.name}` === action.approval.target);
2154
- if (targetField?.type !== "ref")
2155
- continue;
2156
- const target = document.instruments.find((i) => i.id === targetField.target)?.actions[action.approval.action];
2157
- if (!target)
2158
- continue;
2159
- for (const field of target.input) {
2160
- const name = `material_${field.name}`;
2161
- if (!inst.fields.some((f) => f.name === name))
2162
- inst.fields.push({ ...field, name });
2163
- action.approval.input[field.name] = { field: `self.${name}` };
2164
- }
2165
- }
2166
2055
  const changed = new Set();
2167
2056
  for (const decl of program.decls)
2168
2057
  if (decl.kind === "hide" || decl.kind === "expose") {