@hyperscale0/hsx 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/compile.ts CHANGED
@@ -545,17 +545,6 @@ export function compile(
545
545
  }
546
546
  }
547
547
  const origins: CompileOriginMapEntry[] = [];
548
- const materialApprovals = new Set<UdlAction>();
549
- const implicitDecisions = new Map<
550
- string,
551
- {
552
- target: string;
553
- action: string;
554
- party: string;
555
- protectedRequest: string;
556
- origin: Span;
557
- }
558
- >();
559
548
  const resolveFamily = (
560
549
  rawPath: string,
561
550
  expr: { span: Span; source?: string },
@@ -629,7 +618,6 @@ export function compile(
629
618
  arguments_: BlockExpr,
630
619
  origin: Span,
631
620
  inherited = new Map<string, Expr>(),
632
- inheritedApprovers = new Set<string>(),
633
621
  inheritedEnums = new Map<string, string[]>(),
634
622
  attachmentInfo?: {
635
623
  subjectKindId: string;
@@ -822,7 +810,6 @@ export function compile(
822
810
  if (type.kind === "call" && type.name === "enum")
823
811
  enums.set(parameter.key, type.args.map(text));
824
812
  }
825
- const approvers = new Set(inheritedApprovers);
826
813
  const supplied = new Map<string, Expr>();
827
814
  for (const entry of arguments_.entries) {
828
815
  if (supplied.has(entry.key))
@@ -841,8 +828,7 @@ export function compile(
841
828
  param.value.kind === "default" ? param.value.value : undefined;
842
829
  const typeName =
843
830
  type.kind === "type" || type.kind === "call" ? type.name : text(type);
844
- const partyParameter =
845
- attachmentInfo && (typeName === "party" || typeName === "approval");
831
+ const partyParameter = attachmentInfo && typeName === "party";
846
832
  const byName: Expr | undefined =
847
833
  partyParameter &&
848
834
  (subjectPartyRoles.includes(param.key as SubjectPartyRole) ||
@@ -1000,11 +986,7 @@ export function compile(
1000
986
  const v =
1001
987
  (supplied.has(param.key) && !attachmentInfo) || type === "enum"
1002
988
  ? actual
1003
- : resolve(
1004
- actual,
1005
- new Set(),
1006
- !!attachmentInfo && (type === "party" || type === "approval"),
1007
- );
989
+ : resolve(actual, new Set(), !!attachmentInfo && type === "party");
1008
990
  environment.set(param.key, v);
1009
991
  if (type === "enum" && t.kind === "call") {
1010
992
  if (v.kind !== "name" || !t.args.some((a) => text(a) === v.value))
@@ -1016,7 +998,7 @@ export function compile(
1016
998
  } else if (type === "list") {
1017
999
  if (v.kind !== "list")
1018
1000
  fail(v, `${param.key} needs a list`, "write [value, value]");
1019
- } else if (type === "party" || type === "approval") {
1001
+ } else if (type === "party") {
1020
1002
  if (v.kind !== "name" || !isParty(v.value))
1021
1003
  failWithCode(
1022
1004
  actual,
@@ -1026,18 +1008,14 @@ export function compile(
1026
1008
  );
1027
1009
  const party = document.parties[v.value];
1028
1010
  if (
1029
- (type === "approval" && (party?.kind !== "staff" || !party.role)) ||
1030
- (type === "party" &&
1031
- (party?.kind === "staff" ||
1032
- (attachmentInfo && party?.kind === "person")))
1011
+ (party?.kind === "staff" && !party.role) ||
1012
+ (attachmentInfo && party?.kind === "person")
1033
1013
  )
1034
1014
  failWithCode(
1035
1015
  actual,
1036
1016
  "party_kind_mismatch",
1037
1017
  `${param.key} cannot bind ${v.value}`,
1038
- type === "approval"
1039
- ? "use a declared staff party with a role"
1040
- : "use a subject role or declared business",
1018
+ "use a subject role, declared business, or staff with a role",
1041
1019
  );
1042
1020
  resolvedParties.add(param.key);
1043
1021
  if (attachmentInfo)
@@ -1046,7 +1024,6 @@ export function compile(
1046
1024
  )
1047
1025
  ? { role: v.value as SubjectPartyRole }
1048
1026
  : { party: v.value };
1049
- if (type === "approval") approvers.add(text(v));
1050
1027
  } else if (type === "ref") {
1051
1028
  const values = v.kind === "list" ? v.items : [v];
1052
1029
  if (v.kind === "list" && (t.kind !== "type" || !t.many))
@@ -1092,6 +1069,14 @@ export function compile(
1092
1069
  fail(value, "duplicate reference", "list each object once");
1093
1070
  seen.add(value.value);
1094
1071
  }
1072
+ // A many-reference tunable is a list even when one object is bound,
1073
+ // so report datasets and other value positions never see a bare name.
1074
+ if (t.kind === "type" && t.many && v.kind !== "list")
1075
+ environment.set(param.key, {
1076
+ kind: "list",
1077
+ items: [v],
1078
+ span: v.span,
1079
+ });
1095
1080
  } else if (type === "fee" || type === "split" || type === "policy") {
1096
1081
  if (v.kind !== "block")
1097
1082
  fail(v, `${param.key} needs a block`, `write ${param.key} { ... }`);
@@ -1457,6 +1442,14 @@ export function compile(
1457
1442
  fail(row, "reference needs a target", "write ref<object>");
1458
1443
  const [root, ...tail] = target.split(".");
1459
1444
  const resolved = environment.get(root!);
1445
+ // An optional field pointing at an unsupplied optional tunable
1446
+ // has nothing to point at, so the instrument drops it.
1447
+ if (
1448
+ !resolved &&
1449
+ f.optional &&
1450
+ decl.parameters.some((parameter) => parameter.key === root)
1451
+ )
1452
+ continue;
1460
1453
  const resolvedTargets =
1461
1454
  resolved?.kind === "list"
1462
1455
  ? resolved.items
@@ -1788,10 +1781,18 @@ export function compile(
1788
1781
  const adapterList: UdlActionSubject["adapters"] = [];
1789
1782
  for (const entry of subjectBlock.entries) {
1790
1783
  if (entry.key === "adapter") {
1784
+ // A tunable names the adapter; anything else is the binding itself.
1785
+ const adapterName = (item: Expr): string =>
1786
+ item.kind === "name" &&
1787
+ decl.parameters.some(
1788
+ (parameter) => parameter.key === item.value,
1789
+ )
1790
+ ? text(resolve(item))
1791
+ : text(item);
1791
1792
  const bindingNames =
1792
1793
  entry.value.kind === "list"
1793
- ? entry.value.items.map(text)
1794
- : [text(entry.value)];
1794
+ ? entry.value.items.map(adapterName)
1795
+ : [adapterName(entry.value)];
1795
1796
  for (const bindingName of bindingNames) {
1796
1797
  const target = options.adapterRegistry?.[bindingName];
1797
1798
  if (target) {
@@ -2284,13 +2285,6 @@ export function compile(
2284
2285
  "choose create, reserve, post, or void from internal_transfer",
2285
2286
  );
2286
2287
  }
2287
- } else if (key === "approval") {
2288
- const approval = data(expr) as UdlAction["approval"];
2289
- if (approval && (approval.input as unknown) === "material") {
2290
- approval.input = {};
2291
- materialApprovals.add(a);
2292
- }
2293
- a.approval = approval;
2294
2288
  } else (a as unknown as Record<string, unknown>)[key] = data(expr);
2295
2289
  }
2296
2290
  if (attachmentInfo) {
@@ -2328,34 +2322,6 @@ export function compile(
2328
2322
  checkSubjectPaths(a.invoke, row.span);
2329
2323
  currentAction = undefined;
2330
2324
  currentActionName = undefined;
2331
- for (const requirement of a.requires ?? []) {
2332
- if (
2333
- requirement.kind !== "approval" ||
2334
- !approvers.has(requirement.party)
2335
- )
2336
- continue;
2337
- const action = requirement.action ?? name;
2338
- const key = `${id}_${action}_decision`;
2339
- const previous = implicitDecisions.get(key);
2340
- if (
2341
- previous &&
2342
- (previous.party !== requirement.party ||
2343
- previous.protectedRequest !==
2344
- (requirement.protectedRequest ?? "self"))
2345
- )
2346
- fail(
2347
- { span: origin },
2348
- `action ${action} has conflicting approval parties or protected requests`,
2349
- "use a separate decision action for each party",
2350
- );
2351
- implicitDecisions.set(key, {
2352
- target: id,
2353
- action,
2354
- party: requirement.party,
2355
- protectedRequest: requirement.protectedRequest ?? "self",
2356
- origin,
2357
- });
2358
- }
2359
2325
  for (const requirement of a.subject?.requirements ?? []) {
2360
2326
  const entry =
2361
2327
  asBlock(subjectExpr).entries.find(
@@ -2420,7 +2386,6 @@ export function compile(
2420
2386
  ...environment,
2421
2387
  ["parent", { kind: "name", value: id, span: origin } as Expr],
2422
2388
  ]),
2423
- approvers,
2424
2389
  enums,
2425
2390
  attachmentInfo
2426
2391
  ? { ...attachmentInfo, exposed: new Map() }
@@ -2525,7 +2490,6 @@ export function compile(
2525
2490
  tunableBlock,
2526
2491
  entry.span,
2527
2492
  new Map(),
2528
- new Set(),
2529
2493
  new Map(),
2530
2494
  {
2531
2495
  subjectKindId: decl.name,
@@ -2662,7 +2626,6 @@ export function compile(
2662
2626
  decl.body,
2663
2627
  decl.span,
2664
2628
  new Map(),
2665
- new Set(),
2666
2629
  new Map(),
2667
2630
  undefined,
2668
2631
  templateFamily ? { ...templateFamily } : undefined,
@@ -2880,145 +2843,6 @@ export function compile(
2880
2843
  );
2881
2844
  }
2882
2845
  }
2883
- // Approval tunables instantiate the same library record as an explicit decision.
2884
- if (implicitDecisions.size) {
2885
- const content = (
2886
- options.standardLibrary ?? bundledStandardLibrary
2887
- ).source("approvals");
2888
- const parsed = content ? parseProgram(content) : undefined;
2889
- const template = parsed?.program.decls.find(
2890
- (decl) => decl.kind === "instrument" && decl.name === "decision",
2891
- );
2892
- if (
2893
- !template ||
2894
- template.kind !== "instrument" ||
2895
- parsed?.diagnostics.length
2896
- )
2897
- fail(
2898
- program,
2899
- "implicit decisions need the approvals header",
2900
- "provide the standard approvals header",
2901
- );
2902
- for (const [id, decision] of implicitDecisions) {
2903
- if (
2904
- !document.instruments.find((inst) => inst.id === decision.target)
2905
- ?.actions[decision.action]
2906
- )
2907
- continue;
2908
- const existing = document.instruments.some((inst) =>
2909
- Object.values(inst.actions).some(
2910
- (action) =>
2911
- action.approval?.action === decision.action &&
2912
- action.approval.party === decision.party &&
2913
- inst.fields.some((field) => {
2914
- if (
2915
- field.type !== "ref" ||
2916
- field.target !== decision.target ||
2917
- `self.${field.name}` !== action.approval?.target
2918
- )
2919
- return false;
2920
- const [root, name, ...tail] =
2921
- decision.protectedRequest.split(".");
2922
- const input = name && action.approval.input[name];
2923
- const request =
2924
- root === "self"
2925
- ? [action.approval.target, name, ...tail]
2926
- .filter(Boolean)
2927
- .join(".")
2928
- : root === "input" && name
2929
- ? input && "field" in input
2930
- ? [input.field, ...tail].join(".")
2931
- : materialApprovals.has(action)
2932
- ? [`self.material_${name}`, ...tail].join(".")
2933
- : undefined
2934
- : undefined;
2935
- return (
2936
- request !== undefined &&
2937
- action.approval.protectedRequest === request
2938
- );
2939
- }),
2940
- ),
2941
- );
2942
- if (existing) continue;
2943
- if (document.instruments.some((inst) => inst.id === id))
2944
- fail(
2945
- { span: decision.origin },
2946
- `implicit decision name ${id} is already used`,
2947
- "rename the conflicting object",
2948
- );
2949
- const protectedRequest =
2950
- decision.protectedRequest === "self"
2951
- ? "self.target"
2952
- : decision.protectedRequest.startsWith("self.")
2953
- ? `self.target.${decision.protectedRequest.slice(5)}`
2954
- : fail(
2955
- { span: decision.origin },
2956
- "implicit approval needs a stored protected request",
2957
- "declare an explicit decision for an input-based protected request",
2958
- );
2959
- addInstrument(
2960
- template,
2961
- id,
2962
- {
2963
- kind: "block",
2964
- span: decision.origin,
2965
- entries: Object.entries({
2966
- for: decision.target,
2967
- approved_by: decision.party,
2968
- action: decision.action,
2969
- protected_request: protectedRequest,
2970
- }).map(([key, value]) => ({
2971
- key,
2972
- value: {
2973
- kind:
2974
- key === "action" || key === "protected_request"
2975
- ? "text"
2976
- : "name",
2977
- value,
2978
- span: decision.origin,
2979
- },
2980
- span: decision.origin,
2981
- })),
2982
- },
2983
- decision.origin,
2984
- new Map(),
2985
- new Set(),
2986
- new Map(),
2987
- document.instruments.find(
2988
- (instrument) => instrument.id === decision.target,
2989
- )?.subject
2990
- ? {
2991
- subjectKindId: document.instruments.find(
2992
- (instrument) => instrument.id === decision.target,
2993
- )!.subject!,
2994
- attachmentName: id,
2995
- parties: {},
2996
- renames: new Map(),
2997
- exposed: new Map(),
2998
- }
2999
- : undefined,
3000
- );
3001
- }
3002
- }
3003
- // Material approval fields are inferred from the selected action's typed input.
3004
- for (const inst of document.instruments)
3005
- for (const action of Object.values(inst.actions)) {
3006
- if (!materialApprovals.has(action) || !action.approval) continue;
3007
- const targetField = inst.fields.find(
3008
- (f) => `self.${f.name}` === action.approval!.target,
3009
- );
3010
- if (targetField?.type !== "ref") continue;
3011
- const target = document.instruments.find(
3012
- (i) => i.id === targetField.target,
3013
- )?.actions[action.approval.action];
3014
- if (!target) continue;
3015
- for (const field of target.input) {
3016
- const name = `material_${field.name}`;
3017
- if (!inst.fields.some((f) => f.name === name))
3018
- inst.fields.push({ ...field, name });
3019
- action.approval.input[field.name] = { field: `self.${name}` };
3020
- }
3021
- }
3022
2846
  const changed = new Set<string>();
3023
2847
  for (const decl of program.decls)
3024
2848
  if (decl.kind === "hide" || decl.kind === "expose") {
package/src/headers.ts CHANGED
@@ -11,7 +11,6 @@ export const HEADER_NAMES = [
11
11
  "financing",
12
12
  "lending",
13
13
  "insurance",
14
- "approvals",
15
14
  "collections",
16
15
  "travel",
17
16
  "cards",
@@ -144,7 +143,7 @@ export function headerManifest(
144
143
  : [];
145
144
  })(),
146
145
  parties: tunables
147
- .filter((t) => ["party", "approval"].includes(t.type))
146
+ .filter((t) => t.type === "party")
148
147
  .map((t) => t.name),
149
148
  actions: actions(decl.body),
150
149
  };
package/src/lex.ts CHANGED
@@ -26,7 +26,6 @@ export const KEYWORDS = [
26
26
  "by",
27
27
  "for",
28
28
  "is",
29
- "approval",
30
29
  "unique",
31
30
  "on",
32
31
  "count",
package/src/parse.ts CHANGED
@@ -330,20 +330,6 @@ class Parser {
330
330
  return this.node(operator.text);
331
331
  }
332
332
  private requirement(): BlockExpr {
333
- if (this.eat("approval")) {
334
- this.expect("by");
335
- const party = this.atom();
336
- const values: Record<string, Expr> = {
337
- kind: this.node("approval"),
338
- party,
339
- decision: this.node("approved"),
340
- };
341
- if (this.eat("for")) values.action = this.atom();
342
- if (this.eat("is")) values.decision = this.atom();
343
- for (const key of ["protectedRequest", "differentFromInitiator"])
344
- if (this.eat(key)) values[key] = this.atom();
345
- return this.record(values);
346
- }
347
333
  if (this.eat("unique")) {
348
334
  const namespace = this.atom();
349
335
  this.expect("on");