@hyperscale0/hsx 4.0.0 → 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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # HSX 4.1.0
2
+
3
+ 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.
4
+
5
+ # HSX 4.0.1
6
+
7
+ 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.
8
+
1
9
  # HSX 4.0.0
2
10
 
3
11
  HSX adds authored object kinds and attachments with party bindings to owner, actor or operator. Action subject requirements are collected when an attached action runs. The vehicles header is deleted, public instrument create actions are gone, and UDL 3 is rejected. Programs compile to UDL 4; recreate development estates.
@@ -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,CAmoFf"}
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) {
@@ -459,26 +457,35 @@ export function compile(source, options = {}) {
459
457
  }
460
458
  return result;
461
459
  };
462
- const resolveChildExportPath = (parentDecl, suffix) => {
460
+ const collectChildExportPaths = (parentDecl, suffix) => {
463
461
  const recs = entries(asBlock(entries(parentDecl.body).get("records")));
462
+ const matches = [];
464
463
  for (const [recName] of recs) {
465
464
  if (suffix === recName)
466
- return recName;
465
+ matches.push(recName);
467
466
  }
468
467
  for (const [recName, recBlock] of recs) {
469
468
  if (suffix.startsWith(`${recName}_`)) {
470
- const rest = resolveChildExportPath({
469
+ const nested = collectChildExportPaths({
471
470
  kind: "instrument",
472
471
  name: recName,
473
472
  parameters: [],
474
473
  body: asBlock(recBlock),
475
474
  span: parentDecl.span,
476
475
  }, suffix.slice(recName.length + 1));
477
- if (rest)
478
- return `${recName}.${rest}`;
476
+ for (const rest of nested) {
477
+ matches.push(`${recName}.${rest}`);
478
+ }
479
479
  }
480
480
  }
481
- return undefined;
481
+ return matches;
482
+ };
483
+ const resolveChildExportPath = (parentDecl, suffix) => {
484
+ const matches = collectChildExportPaths(parentDecl, suffix);
485
+ if (matches.length > 1) {
486
+ failWithCode(parentDecl, "HSX1001", `ambiguous child export path suffix '${suffix}': multiple candidates (${matches.join(", ")})`, "rename conflicting records to remove duplicate export path suffixes");
487
+ }
488
+ return matches[0];
482
489
  };
483
490
  const getInstrumentFamily = (targetId) => {
484
491
  const existing = document.instruments.find((i) => i.id === targetId);
@@ -542,7 +549,6 @@ export function compile(source, options = {}) {
542
549
  if (type.kind === "call" && type.name === "enum")
543
550
  enums.set(parameter.key, type.args.map(text));
544
551
  }
545
- const approvers = new Set(inheritedApprovers);
546
552
  const supplied = new Map();
547
553
  for (const entry of arguments_.entries) {
548
554
  if (supplied.has(entry.key))
@@ -554,7 +560,7 @@ export function compile(source, options = {}) {
554
560
  const type = param.value.kind === "default" ? param.value.type : param.value;
555
561
  const fallback = param.value.kind === "default" ? param.value.value : undefined;
556
562
  const typeName = type.kind === "type" || type.kind === "call" ? type.name : text(type);
557
- const partyParameter = attachmentInfo && (typeName === "party" || typeName === "approval");
563
+ const partyParameter = attachmentInfo && typeName === "party";
558
564
  const byName = partyParameter &&
559
565
  (subjectPartyRoles.includes(param.key) ||
560
566
  document.parties[param.key])
@@ -662,7 +668,7 @@ export function compile(source, options = {}) {
662
668
  const type = t.kind === "type" || t.kind === "call" ? t.name : text(t);
663
669
  const v = (supplied.has(param.key) && !attachmentInfo) || type === "enum"
664
670
  ? actual
665
- : resolve(actual, new Set(), !!attachmentInfo && (type === "party" || type === "approval"));
671
+ : resolve(actual, new Set(), !!attachmentInfo && type === "party");
666
672
  environment.set(param.key, v);
667
673
  if (type === "enum" && t.kind === "call") {
668
674
  if (v.kind !== "name" || !t.args.some((a) => text(a) === v.value))
@@ -672,24 +678,18 @@ export function compile(source, options = {}) {
672
678
  if (v.kind !== "list")
673
679
  fail(v, `${param.key} needs a list`, "write [value, value]");
674
680
  }
675
- else if (type === "party" || type === "approval") {
681
+ else if (type === "party") {
676
682
  if (v.kind !== "name" || !isParty(v.value))
677
683
  failWithCode(actual, attachmentInfo ? "subject_party_unbound" : "HSX1001", `${param.key} needs a declared party`, "declare a party and use its name here");
678
684
  const party = document.parties[v.value];
679
- if ((type === "approval" && (party?.kind !== "staff" || !party.role)) ||
680
- (type === "party" &&
681
- (party?.kind === "staff" ||
682
- (attachmentInfo && party?.kind === "person"))))
683
- failWithCode(actual, "party_kind_mismatch", `${param.key} cannot bind ${v.value}`, type === "approval"
684
- ? "use a declared staff party with a role"
685
- : "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");
686
688
  resolvedParties.add(param.key);
687
689
  if (attachmentInfo)
688
690
  attachmentInfo.parties[param.key] = subjectPartyRoles.includes(v.value)
689
691
  ? { role: v.value }
690
692
  : { party: v.value };
691
- if (type === "approval")
692
- approvers.add(text(v));
693
693
  }
694
694
  else if (type === "ref") {
695
695
  const values = v.kind === "list" ? v.items : [v];
@@ -716,6 +716,14 @@ export function compile(source, options = {}) {
716
716
  fail(value, "duplicate reference", "list each object once");
717
717
  seen.add(value.value);
718
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
+ });
719
727
  }
720
728
  else if (type === "fee" || type === "split" || type === "policy") {
721
729
  if (v.kind !== "block")
@@ -1000,6 +1008,12 @@ export function compile(source, options = {}) {
1000
1008
  fail(row, "reference needs a target", "write ref<object>");
1001
1009
  const [root, ...tail] = target.split(".");
1002
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;
1003
1017
  const resolvedTargets = resolved?.kind === "list"
1004
1018
  ? resolved.items
1005
1019
  : resolved
@@ -1263,9 +1277,14 @@ export function compile(source, options = {}) {
1263
1277
  const adapterList = [];
1264
1278
  for (const entry of subjectBlock.entries) {
1265
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);
1266
1285
  const bindingNames = entry.value.kind === "list"
1267
- ? entry.value.items.map(text)
1268
- : [text(entry.value)];
1286
+ ? entry.value.items.map(adapterName)
1287
+ : [adapterName(entry.value)];
1269
1288
  for (const bindingName of bindingNames) {
1270
1289
  const target = options.adapterRegistry?.[bindingName];
1271
1290
  if (target) {
@@ -1617,14 +1636,6 @@ export function compile(source, options = {}) {
1617
1636
  fail(move, `unknown transfer instruction ${op}`, "choose create, reserve, post, or void from internal_transfer");
1618
1637
  }
1619
1638
  }
1620
- else if (key === "approval") {
1621
- const approval = data(expr);
1622
- if (approval && approval.input === "material") {
1623
- approval.input = {};
1624
- materialApprovals.add(a);
1625
- }
1626
- a.approval = approval;
1627
- }
1628
1639
  else
1629
1640
  a[key] = data(expr);
1630
1641
  }
@@ -1662,26 +1673,6 @@ export function compile(source, options = {}) {
1662
1673
  checkSubjectPaths(a.invoke, row.span);
1663
1674
  currentAction = undefined;
1664
1675
  currentActionName = undefined;
1665
- for (const requirement of a.requires ?? []) {
1666
- if (requirement.kind !== "approval" ||
1667
- !approvers.has(requirement.party))
1668
- continue;
1669
- const action = requirement.action ?? name;
1670
- const key = `${id}_${action}_decision`;
1671
- const previous = implicitDecisions.get(key);
1672
- if (previous &&
1673
- (previous.party !== requirement.party ||
1674
- previous.protectedRequest !==
1675
- (requirement.protectedRequest ?? "self")))
1676
- fail({ span: origin }, `action ${action} has conflicting approval parties or protected requests`, "use a separate decision action for each party");
1677
- implicitDecisions.set(key, {
1678
- target: id,
1679
- action,
1680
- party: requirement.party,
1681
- protectedRequest: requirement.protectedRequest ?? "self",
1682
- origin,
1683
- });
1684
- }
1685
1676
  for (const requirement of a.subject?.requirements ?? []) {
1686
1677
  const entry = asBlock(subjectExpr).entries.find((entry) => entry.key === requirement.field.name) ??
1687
1678
  asBlock(subjectExpr).entries.find((entry) => entry.key === "adapter");
@@ -1730,7 +1721,7 @@ export function compile(source, options = {}) {
1730
1721
  addInstrument(child, `${id}_${key}`, emptyBlock, origin, new Map([
1731
1722
  ...environment,
1732
1723
  ["parent", { kind: "name", value: id, span: origin }],
1733
- ]), approvers, enums, attachmentInfo
1724
+ ]), enums, attachmentInfo
1734
1725
  ? { ...attachmentInfo, exposed: new Map() }
1735
1726
  : undefined, childFamily);
1736
1727
  }
@@ -1803,7 +1794,7 @@ export function compile(source, options = {}) {
1803
1794
  span: entry.value.span,
1804
1795
  };
1805
1796
  const templateFamily = resolveFamily(targetTemplate, entry, false);
1806
- addInstrument(template, instId, tunableBlock, entry.span, new Map(), new Set(), new Map(), {
1797
+ addInstrument(template, instId, tunableBlock, entry.span, new Map(), new Map(), {
1807
1798
  subjectKindId: decl.name,
1808
1799
  attachmentName,
1809
1800
  renames,
@@ -1892,7 +1883,7 @@ export function compile(source, options = {}) {
1892
1883
  if (!template)
1893
1884
  fail(decl, `unknown instrument ${decl.target}`, `add use ${decl.target.split(".")[0]} and choose a declared instrument`);
1894
1885
  const templateFamily = resolveFamily(decl.target, decl, false);
1895
- 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);
1896
1887
  }
1897
1888
  if (decl.kind === "object") {
1898
1889
  compileObject(decl);
@@ -2061,99 +2052,6 @@ export function compile(source, options = {}) {
2061
2052
  : field.target).some((id) => eliminatedStates.get(id)?.has(state)));
2062
2053
  }
2063
2054
  }
2064
- // Approval tunables instantiate the same library record as an explicit decision.
2065
- if (implicitDecisions.size) {
2066
- const content = (options.standardLibrary ?? bundledStandardLibrary).source("approvals");
2067
- const parsed = content ? parseProgram(content) : undefined;
2068
- const template = parsed?.program.decls.find((decl) => decl.kind === "instrument" && decl.name === "decision");
2069
- if (!template ||
2070
- template.kind !== "instrument" ||
2071
- parsed?.diagnostics.length)
2072
- fail(program, "implicit decisions need the approvals header", "provide the standard approvals header");
2073
- for (const [id, decision] of implicitDecisions) {
2074
- if (!document.instruments.find((inst) => inst.id === decision.target)
2075
- ?.actions[decision.action])
2076
- continue;
2077
- const existing = document.instruments.some((inst) => Object.values(inst.actions).some((action) => action.approval?.action === decision.action &&
2078
- action.approval.party === decision.party &&
2079
- inst.fields.some((field) => {
2080
- if (field.type !== "ref" ||
2081
- field.target !== decision.target ||
2082
- `self.${field.name}` !== action.approval?.target)
2083
- return false;
2084
- const [root, name, ...tail] = decision.protectedRequest.split(".");
2085
- const input = name && action.approval.input[name];
2086
- const request = root === "self"
2087
- ? [action.approval.target, name, ...tail]
2088
- .filter(Boolean)
2089
- .join(".")
2090
- : root === "input" && name
2091
- ? input && "field" in input
2092
- ? [input.field, ...tail].join(".")
2093
- : materialApprovals.has(action)
2094
- ? [`self.material_${name}`, ...tail].join(".")
2095
- : undefined
2096
- : undefined;
2097
- return (request !== undefined &&
2098
- action.approval.protectedRequest === request);
2099
- })));
2100
- if (existing)
2101
- continue;
2102
- if (document.instruments.some((inst) => inst.id === id))
2103
- fail({ span: decision.origin }, `implicit decision name ${id} is already used`, "rename the conflicting object");
2104
- const protectedRequest = decision.protectedRequest === "self"
2105
- ? "self.target"
2106
- : decision.protectedRequest.startsWith("self.")
2107
- ? `self.target.${decision.protectedRequest.slice(5)}`
2108
- : fail({ span: decision.origin }, "implicit approval needs a stored protected request", "declare an explicit decision for an input-based protected request");
2109
- addInstrument(template, id, {
2110
- kind: "block",
2111
- span: decision.origin,
2112
- entries: Object.entries({
2113
- for: decision.target,
2114
- approved_by: decision.party,
2115
- action: decision.action,
2116
- protected_request: protectedRequest,
2117
- }).map(([key, value]) => ({
2118
- key,
2119
- value: {
2120
- kind: key === "action" || key === "protected_request"
2121
- ? "text"
2122
- : "name",
2123
- value,
2124
- span: decision.origin,
2125
- },
2126
- span: decision.origin,
2127
- })),
2128
- }, decision.origin, new Map(), new Set(), new Map(), document.instruments.find((instrument) => instrument.id === decision.target)?.subject
2129
- ? {
2130
- subjectKindId: document.instruments.find((instrument) => instrument.id === decision.target).subject,
2131
- attachmentName: id,
2132
- parties: {},
2133
- renames: new Map(),
2134
- exposed: new Map(),
2135
- }
2136
- : undefined);
2137
- }
2138
- }
2139
- // Material approval fields are inferred from the selected action's typed input.
2140
- for (const inst of document.instruments)
2141
- for (const action of Object.values(inst.actions)) {
2142
- if (!materialApprovals.has(action) || !action.approval)
2143
- continue;
2144
- const targetField = inst.fields.find((f) => `self.${f.name}` === action.approval.target);
2145
- if (targetField?.type !== "ref")
2146
- continue;
2147
- const target = document.instruments.find((i) => i.id === targetField.target)?.actions[action.approval.action];
2148
- if (!target)
2149
- continue;
2150
- for (const field of target.input) {
2151
- const name = `material_${field.name}`;
2152
- if (!inst.fields.some((f) => f.name === name))
2153
- inst.fields.push({ ...field, name });
2154
- action.approval.input[field.name] = { field: `self.${name}` };
2155
- }
2156
- }
2157
2055
  const changed = new Set();
2158
2056
  for (const decl of program.decls)
2159
2057
  if (decl.kind === "hide" || decl.kind === "expose") {