@altopelago/aeos-core 0.10.0 → 0.12.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.
Files changed (42) hide show
  1. package/README.md +36 -0
  2. package/dist/index.d.ts +5 -1
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/rules/numericForm.d.ts.map +1 -1
  7. package/dist/rules/numericForm.js +1 -9
  8. package/dist/rules/numericForm.js.map +1 -1
  9. package/dist/rules/presence.d.ts.map +1 -1
  10. package/dist/rules/presence.js +0 -2
  11. package/dist/rules/presence.js.map +1 -1
  12. package/dist/rules/referenceForm.d.ts.map +1 -1
  13. package/dist/rules/referenceForm.js +9 -3
  14. package/dist/rules/referenceForm.js.map +1 -1
  15. package/dist/rules/schemaIndex.d.ts.map +1 -1
  16. package/dist/rules/schemaIndex.js +27 -2
  17. package/dist/rules/schemaIndex.js.map +1 -1
  18. package/dist/rules/stringForm.js +2 -1
  19. package/dist/rules/stringForm.js.map +1 -1
  20. package/dist/rules/typeCheck.d.ts +1 -0
  21. package/dist/rules/typeCheck.d.ts.map +1 -1
  22. package/dist/rules/typeCheck.js +7 -2
  23. package/dist/rules/typeCheck.js.map +1 -1
  24. package/dist/schema-codec.d.ts +19 -0
  25. package/dist/schema-codec.d.ts.map +1 -0
  26. package/dist/schema-codec.js +424 -0
  27. package/dist/schema-codec.js.map +1 -0
  28. package/dist/schema-tools.d.ts +90 -0
  29. package/dist/schema-tools.d.ts.map +1 -0
  30. package/dist/schema-tools.js +465 -0
  31. package/dist/schema-tools.js.map +1 -0
  32. package/dist/types/schema.d.ts +25 -4
  33. package/dist/types/schema.d.ts.map +1 -1
  34. package/dist/types/schema.js.map +1 -1
  35. package/dist/util/datatypes.d.ts +13 -0
  36. package/dist/util/datatypes.d.ts.map +1 -0
  37. package/dist/util/datatypes.js +50 -0
  38. package/dist/util/datatypes.js.map +1 -0
  39. package/dist/validate.d.ts.map +1 -1
  40. package/dist/validate.js +123 -198
  41. package/dist/validate.js.map +1 -1
  42. package/package.json +8 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datatypes.js","sourceRoot":"","sources":["../../src/util/datatypes.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IACzC,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,CAAC,UAAU,EAAE,YAAY,CAAC;SACpC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;SACzB,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,GAAG,CAAY,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,EAAE,CAAC;QACtC,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAA4B,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC;IACtH,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAA4B;IAClE,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,WAAW,EAAE,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnD,MAAM,KAAK,GAAG,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,KAAK;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,YAAY,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAW1D,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAkDzE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAErE,+DAA+D;IAC/D,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC;CAE9C;AAgJD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CACpB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,QAAQ,EAChB,OAAO,GAAE,eAAoB,GAC9B,cAAc,CAuWhB"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,EAAc,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAY1D,OAAO,KAAK,EAAiB,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AA4EzE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,gCAAgC,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAErE,+DAA+D;IAC/D,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC;CAE9C;AAiJD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,QAAQ,CACpB,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,QAAQ,EAChB,OAAO,GAAE,eAAoB,GAC9B,cAAc,CAqWhB"}
package/dist/validate.js CHANGED
@@ -13,6 +13,8 @@ import { checkTypes } from './rules/typeCheck.js';
13
13
  import { checkReferenceForms } from './rules/referenceForm.js';
14
14
  import { checkNumericForm } from './rules/numericForm.js';
15
15
  import { checkStringForm, checkPatterns, matchesPortablePattern } from './rules/stringForm.js';
16
+ import { datatypeBase, declaredRadixFromDatatype, parseClarifierValues } from './util/datatypes.js';
17
+ import { parseAddress, resolveAddress, } from '@altopelago/sansa';
16
18
  const TYPE_ALIASES = {
17
19
  NumberLiteral: ['NumberLiteral'],
18
20
  StringLiteral: ['StringLiteral'],
@@ -29,10 +31,11 @@ const TYPE_ALIASES = {
29
31
  RadixLiteral: ['RadixLiteral'],
30
32
  EncodingLiteral: ['EncodingLiteral'],
31
33
  SeparatorLiteral: ['SeparatorLiteral'],
34
+ SansaAddressLiteral: ['SansaAddressLiteral'],
32
35
  DateLiteral: ['DateLiteral'],
33
36
  TimeLiteral: ['TimeLiteral'],
34
37
  DateTimeLiteral: ['DateTimeLiteral'],
35
- ZRUTDateTimeLiteral: ['ZRUTDateTimeLiteral'],
38
+ WTCDateTimeLiteral: ['WTCDateTimeLiteral'],
36
39
  CloneReference: ['CloneReference'],
37
40
  PointerReference: ['PointerReference'],
38
41
  NodeLiteral: ['NodeLiteral'],
@@ -40,6 +43,15 @@ const TYPE_ALIASES = {
40
43
  function formatQuotedMemberSegment(key) {
41
44
  return `.[${JSON.stringify(String(key))}]`;
42
45
  }
46
+ function formatMemberSelector(key) {
47
+ const value = String(key);
48
+ return /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)
49
+ ? `.${value}`
50
+ : formatQuotedMemberSegment(value);
51
+ }
52
+ function appendAttributePath(basePath, key) {
53
+ return `${basePath}.@${formatMemberSelector(key)}`;
54
+ }
43
55
  const DEFAULT_RESOURCE_POLICY = {
44
56
  max_events: 100_000,
45
57
  max_rules: 10_000,
@@ -89,13 +101,14 @@ const STRING_LIKE_VALUE_TYPES = new Set([
89
101
  'StringLiteral',
90
102
  'TrimtickLiteral',
91
103
  'SeparatorLiteral',
104
+ 'SansaAddressLiteral',
92
105
  'HexLiteral',
93
106
  'EncodingLiteral',
94
107
  'NullLiteral',
95
108
  'DateLiteral',
96
109
  'TimeLiteral',
97
110
  'DateTimeLiteral',
98
- 'ZRUTDateTimeLiteral',
111
+ 'WTCDateTimeLiteral',
99
112
  ]);
100
113
  function stringLikePayloadLength(event) {
101
114
  if (!STRING_LIKE_VALUE_TYPES.has(event.type))
@@ -109,7 +122,7 @@ function enforceStringLengthResourceBudget(info, path, policy, ctx) {
109
122
  emitResourceError(ctx, path, `String-like payload length ${payloadLength} exceeds max_string_length_default ${policy.max_string_length_default}`, info.span);
110
123
  }
111
124
  for (const [key, attribute] of info.attributes ?? []) {
112
- enforceStringLengthResourceBudget(attribute, `${path}@${key}`, policy, ctx);
125
+ enforceStringLengthResourceBudget(attribute, appendAttributePath(path, key), policy, ctx);
113
126
  }
114
127
  }
115
128
  function inspectSchemaResourceShape(schema, policy, ctx) {
@@ -143,7 +156,7 @@ function inspectConstraintResourceShape(constraints, path, depth, policy, ctx) {
143
156
  }
144
157
  if (constraints.attributes !== undefined) {
145
158
  for (const [key, child] of Object.entries(constraints.attributes)) {
146
- inspectConstraintResourceShape(child, `${path}@${key}`, depth + 1, policy, ctx);
159
+ inspectConstraintResourceShape(child, appendAttributePath(path, key), depth + 1, policy, ctx);
147
160
  }
148
161
  }
149
162
  }
@@ -231,24 +244,9 @@ export function validate(aes, schema, options = {}) {
231
244
  function decodeSeparatorChars(datatype) {
232
245
  if (!datatype)
233
246
  return [];
234
- const match = datatype.match(/\[([^\]]*)\]$/);
235
- if (!match)
236
- return [];
237
- const payload = match[1] ?? '';
238
- if (payload.length === 0)
247
+ if (datatypeBase(datatype).toLowerCase() !== 'sep')
239
248
  return [];
240
- const separators = [];
241
- let i = 0;
242
- while (i < payload.length) {
243
- separators.push(payload[i]);
244
- i += 1;
245
- if (i < payload.length) {
246
- if (payload[i] !== ',')
247
- return [];
248
- i += 1;
249
- }
250
- }
251
- return separators;
249
+ return parseClarifierValues(datatype).filter((value) => typeof value === 'string');
252
250
  }
253
251
  // Phase 2 — Baseline invariants
254
252
  const seen = new Map();
@@ -274,6 +272,7 @@ export function validate(aes, schema, options = {}) {
274
272
  ...(attributes ? { attributes } : {}),
275
273
  };
276
274
  eventsByPath.set(elementPath, info);
275
+ addAttributeEvents(elementPath, attributes);
277
276
  }
278
277
  }
279
278
  function buildAttributeInfoMap(attributes) {
@@ -300,6 +299,22 @@ export function validate(aes, schema, options = {}) {
300
299
  }
301
300
  return mapped;
302
301
  }
302
+ function addAttributeEvents(basePath, attributes) {
303
+ if (!attributes)
304
+ return;
305
+ for (const [key, attribute] of attributes.entries()) {
306
+ const attributePath = appendAttributePath(basePath, key);
307
+ eventsByPath.set(attributePath, {
308
+ type: attribute.type,
309
+ raw: attribute.raw,
310
+ value: attribute.value,
311
+ ...(attribute.datatype !== undefined ? { datatype: attribute.datatype } : {}),
312
+ span: attribute.span,
313
+ ...(attribute.attributes ? { attributes: attribute.attributes } : {}),
314
+ });
315
+ addAttributeEvents(attributePath, attribute.attributes);
316
+ }
317
+ }
303
318
  for (let i = 0; i < aes.length; i++) {
304
319
  const event = aes[i];
305
320
  const pathStr = formatCanonicalPath(event.path);
@@ -338,6 +353,7 @@ export function validate(aes, schema, options = {}) {
338
353
  ...(attributes ? { attributes } : {}),
339
354
  };
340
355
  eventsByPath.set(pathStr, info);
356
+ addAttributeEvents(pathStr, attributes);
341
357
  if ((event.value.type === 'TupleLiteral' || event.value.type === 'ListLiteral' || event.value.type === 'ListNode')
342
358
  && Array.isArray(event.value.elements)) {
343
359
  containerArity.set(pathStr, event.value.elements.length);
@@ -391,12 +407,12 @@ export function validate(aes, schema, options = {}) {
391
407
  // Phase 3: Build rule index from schema (run after baseline invariants)
392
408
  const ruleIndex = buildRuleIndex(schema, ctx);
393
409
  const selectorExpansionBudget = { count: 0 };
394
- const expandedRuleIndex = expandWildcardRules(expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolicy, selectorExpansionBudget), eventsByPath, ctx, resourcePolicy, selectorExpansionBudget);
410
+ const expandedRuleIndex = expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolicy, selectorExpansionBudget);
395
411
  const effectiveRuleIndex = mergeDatatypeRules(expandedRuleIndex, schema.datatype_rules, eventsByPath);
396
412
  // Phase 4: Presence checks (required fields)
397
- const boundPaths = new Set(seen.keys());
413
+ const boundPaths = new Set(eventsByPath.keys());
398
414
  checkPresence(effectiveRuleIndex, boundPaths, ctx);
399
- checkWorldPolicy(schema, aes, boundPaths, ctx);
415
+ checkWorldPolicy(schema, aes, boundPaths, eventsByPath, ctx);
400
416
  // Phase 5: Type checks (literal kind)
401
417
  checkReferenceForms(schema, effectiveRuleIndex, eventsByPath, ctx);
402
418
  const effectiveEventsByPath = resolveReferenceFormEvents(effectiveRuleIndex, eventsByPath, resourcePolicy, ctx);
@@ -494,7 +510,7 @@ export function validate(aes, schema, options = {}) {
494
510
  }
495
511
  return createPassingEnvelope(guarantees, ctx.warnings);
496
512
  }
497
- function checkWorldPolicy(schema, aes, boundPaths, ctx) {
513
+ function checkWorldPolicy(schema, aes, boundPaths, eventsByPath, ctx) {
498
514
  if ((schema.world ?? 'open') !== 'closed')
499
515
  return;
500
516
  const allowedRules = schema.rules
@@ -504,6 +520,12 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
504
520
  ? { kind: 'selector', value: rule.selector }
505
521
  : null)
506
522
  .filter((rule) => rule !== null);
523
+ const selectorMatches = new Map();
524
+ for (const rule of allowedRules) {
525
+ if (rule.kind !== 'selector' || selectorMatches.has(rule.value))
526
+ continue;
527
+ selectorMatches.set(rule.value, resolveSansaSelectorPathSet(rule.value, eventsByPath, ctx));
528
+ }
507
529
  for (const event of aes) {
508
530
  const key = typeof event.key === 'string' ? event.key : '';
509
531
  if (key.startsWith('aeon:'))
@@ -512,7 +534,7 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
512
534
  if (!boundPaths.has(path))
513
535
  continue;
514
536
  if (allowedRules.some((rule) => rule.kind === 'selector'
515
- ? matchesSelectorPath(path, rule.value)
537
+ ? selectorMatches.get(rule.value)?.has(path) === true
516
538
  : matchesAllowedPath(path, rule.value)))
517
539
  continue;
518
540
  emitError(ctx, createDiag(path, toTupleLocal(event.span), `Binding '${path}' is not allowed by closed-world schema`, ErrorCodes.UNEXPECTED_BINDING));
@@ -549,10 +571,11 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolic
549
571
  continue;
550
572
  if (typeof rule.path === 'string' && rule.path.length > 0)
551
573
  continue;
574
+ const resolvedPaths = resolveSansaSelectorPathSet(rule.selector, eventsByPath, ctx);
575
+ if (resolvedPaths === null)
576
+ continue;
552
577
  let matched = false;
553
- for (const actualPath of eventsByPath.keys()) {
554
- if (!matchesSelectorPath(actualPath, rule.selector))
555
- continue;
578
+ for (const actualPath of resolvedPaths) {
556
579
  matched = true;
557
580
  expansionBudget.count += 1;
558
581
  if (expansionBudget.count > resourcePolicy.max_selector_expansions) {
@@ -569,25 +592,6 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolic
569
592
  }
570
593
  return expanded;
571
594
  }
572
- function expandWildcardRules(ruleIndex, eventsByPath, ctx, resourcePolicy, expansionBudget) {
573
- const expanded = new Map(ruleIndex);
574
- for (const [path, rule] of ruleIndex.entries()) {
575
- if (!path.includes('[*]'))
576
- continue;
577
- expanded.delete(path);
578
- for (const actualPath of eventsByPath.keys()) {
579
- if (matchesAllowedPath(actualPath, path)) {
580
- expansionBudget.count += 1;
581
- if (expansionBudget.count > resourcePolicy.max_selector_expansions) {
582
- emitResourceError(ctx, path, `Wildcard expansion count exceeds max_selector_expansions ${resourcePolicy.max_selector_expansions}`);
583
- return expanded;
584
- }
585
- expanded.set(actualPath, { ...rule, path: actualPath });
586
- }
587
- }
588
- }
589
- return expanded;
590
- }
591
595
  function selectAnyOfRules(ruleIndex, eventsByPath, ctx) {
592
596
  const selected = new Map(ruleIndex);
593
597
  for (const [path, rule] of ruleIndex.entries()) {
@@ -701,156 +705,93 @@ function formatReferenceLookupPath(segments) {
701
705
  : formatQuotedMemberSegment(segment);
702
706
  continue;
703
707
  }
704
- out += /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(segment.key)
705
- ? `@${segment.key}`
706
- : `@[${JSON.stringify(segment.key)}]`;
708
+ out = appendAttributePath(out, segment.key);
707
709
  }
708
710
  return out;
709
711
  }
710
712
  function matchesAllowedPath(actualPath, allowedPath) {
711
- if (actualPath === allowedPath)
712
- return true;
713
- // Closed-world schemas may allow list descendants via canonical wildcard paths
714
- // such as `$.items[*]` or `$.items[*].x`.
715
- if (!allowedPath.includes('[*]'))
716
- return false;
717
- const escaped = allowedPath
718
- .split('[*]')
719
- .map((part) => part.replace(/[|\\{}()[\]^$+?.]/g, '\\$&'))
720
- .join('\\[\\d+\\]');
721
- const pattern = `^${escaped}$`;
722
- return new RegExp(pattern).test(actualPath);
713
+ return actualPath === allowedPath;
723
714
  }
724
- function tokenizeCanonicalLikePath(path) {
725
- if (!path.startsWith('$'))
726
- return null;
727
- const segments = [];
728
- let index = 1;
729
- while (index < path.length) {
730
- const marker = path[index];
731
- if (marker === '.') {
732
- index += 1;
733
- if (path[index] === '[') {
734
- const end = findBracketEnd(path, index);
735
- if (end < 0)
736
- return null;
737
- segments.push(path.slice(index, end + 1));
738
- index = end + 1;
739
- continue;
740
- }
741
- const start = index;
742
- while (index < path.length && !['.', '[', '@'].includes(path[index])) {
743
- index += 1;
744
- }
745
- if (start === index)
746
- return null;
747
- segments.push(path.slice(start, index));
748
- continue;
749
- }
750
- if (marker === '[') {
751
- const end = findBracketEnd(path, index);
752
- if (end < 0)
753
- return null;
754
- segments.push(path.slice(index, end + 1));
755
- index = end + 1;
756
- continue;
757
- }
758
- if (marker === '@') {
759
- index += 1;
760
- if (path[index] === '[') {
761
- const end = findBracketEnd(path, index);
762
- if (end < 0)
763
- return null;
764
- segments.push(`@${path.slice(index, end + 1)}`);
765
- index = end + 1;
766
- continue;
767
- }
768
- const start = index;
769
- while (index < path.length && !['.', '[', '@'].includes(path[index])) {
770
- index += 1;
771
- }
772
- if (start === index)
773
- return null;
774
- segments.push(`@${path.slice(start, index)}`);
775
- continue;
776
- }
715
+ function resolveSansaSelectorPathSet(selector, eventsByPath, ctx) {
716
+ const result = resolveAddress(selector, createAeosSansaResolveNamespace(eventsByPath));
717
+ if (!result.ok) {
718
+ const first = result.errors[0];
719
+ emitError(ctx, createDiag(selector, null, first ? `Invalid or unsupported SANSA selector: ${first.message}` : `Invalid or unsupported SANSA selector: ${selector}`, ErrorCodes.INVALID_SCHEMA_POLICY));
777
720
  return null;
778
721
  }
779
- return segments;
722
+ return new Set(result.bindings.map((binding) => binding.path).filter((path) => eventsByPath.has(path)));
780
723
  }
781
- function findBracketEnd(path, start) {
782
- let quote = null;
783
- let escaped = false;
784
- for (let index = start + 1; index < path.length; index++) {
785
- const ch = path[index];
786
- if (escaped) {
787
- escaped = false;
788
- continue;
789
- }
790
- if (quote) {
791
- if (ch === '\\') {
792
- escaped = true;
793
- }
794
- else if (ch === quote) {
795
- quote = null;
796
- }
797
- continue;
798
- }
799
- if (ch === '"' || ch === "'") {
800
- quote = ch;
801
- continue;
802
- }
803
- if (ch === ']')
804
- return index;
724
+ function createAeosSansaResolveNamespace(eventsByPath) {
725
+ const root = buildAeosSansaResolveTree(eventsByPath);
726
+ return {
727
+ root,
728
+ children: (binding) => binding.children,
729
+ member: (binding, name) => binding.children.find((child) => child.name === name),
730
+ position: (binding, index) => binding.children.find((child) => child.index === index),
731
+ attributeSpace: (binding) => binding.attributeSpace,
732
+ name: (binding) => binding.name,
733
+ index: (binding) => binding.index,
734
+ semanticType: (binding) => binding.info?.datatype,
735
+ representationKind: (binding) => binding.info?.type,
736
+ };
737
+ }
738
+ function buildAeosSansaResolveTree(eventsByPath) {
739
+ const root = { path: '$', children: [] };
740
+ for (const [path, info] of [...eventsByPath.entries()].sort((a, b) => a[0].length - b[0].length)) {
741
+ insertAeosSansaResolvePath(root, path, info);
805
742
  }
806
- return -1;
743
+ return root;
807
744
  }
808
- function matchesSelectorPath(actualPath, selector) {
809
- if (actualPath === selector)
810
- return true;
811
- const actualSegments = tokenizeCanonicalLikePath(actualPath);
812
- const selectorSegments = tokenizeCanonicalLikePath(selector);
813
- if (!actualSegments || !selectorSegments)
814
- return false;
815
- const matchFrom = (actualIndex, selectorIndex) => {
816
- if (selectorIndex === selectorSegments.length) {
817
- return actualIndex === actualSegments.length;
818
- }
819
- const selectorSegment = selectorSegments[selectorIndex];
820
- if (selectorSegment === '**') {
821
- if (selectorIndex === selectorSegments.length - 1)
822
- return true;
823
- for (let nextActual = actualIndex; nextActual <= actualSegments.length; nextActual++) {
824
- if (matchFrom(nextActual, selectorIndex + 1))
825
- return true;
826
- }
827
- return false;
828
- }
829
- if (actualIndex >= actualSegments.length)
830
- return false;
831
- if (selectorSegment === '*') {
832
- return matchFrom(actualIndex + 1, selectorIndex + 1);
833
- }
834
- if (selectorSegment === '[*]') {
835
- return /^\[\d+\]$/.test(actualSegments[actualIndex])
836
- && matchFrom(actualIndex + 1, selectorIndex + 1);
745
+ function insertAeosSansaResolvePath(root, path, info) {
746
+ const parsed = parseAddress(path);
747
+ if (!parsed.ok)
748
+ return;
749
+ if (parsed.address.root.kind !== 'absolute')
750
+ return;
751
+ let current = root;
752
+ let currentPath = '$';
753
+ for (const selector of parsed.address.selectors) {
754
+ switch (selector.type) {
755
+ case 'member':
756
+ currentPath += formatMemberSelector(selector.name);
757
+ current = getOrCreateChildBinding(current, currentPath, { name: selector.name });
758
+ break;
759
+ case 'position':
760
+ currentPath += `[${selector.index}]`;
761
+ current = getOrCreateChildBinding(current, currentPath, { index: selector.index });
762
+ break;
763
+ case 'attributeSpace':
764
+ currentPath += '.@';
765
+ current.attributeSpace ??= { path: currentPath, children: [] };
766
+ current = current.attributeSpace;
767
+ break;
768
+ default:
769
+ return;
837
770
  }
838
- return selectorSegment === actualSegments[actualIndex]
839
- && matchFrom(actualIndex + 1, selectorIndex + 1);
840
- };
841
- return matchFrom(0, 0);
771
+ }
772
+ current.info = info;
773
+ }
774
+ function getOrCreateChildBinding(parent, path, identity) {
775
+ const existing = parent.children.find((child) => identity.name !== undefined
776
+ ? child.name === identity.name
777
+ : child.index === identity.index);
778
+ if (existing)
779
+ return existing;
780
+ const child = { path, children: [], ...identity };
781
+ parent.children.push(child);
782
+ return child;
842
783
  }
843
784
  function collectAllowedAttributePaths(ruleIndex) {
844
785
  const allowed = [];
845
786
  function visit(basePath, constraints) {
846
- if (basePath.includes('@')) {
787
+ if (basePath.includes('.@.')) {
847
788
  allowed.push(basePath);
848
789
  }
849
790
  const attributes = constraints.attributes;
850
791
  if (!attributes)
851
792
  return;
852
793
  for (const [key, childConstraints] of Object.entries(attributes)) {
853
- visit(`${basePath}@${key}`, childConstraints);
794
+ visit(appendAttributePath(basePath, key), childConstraints);
854
795
  }
855
796
  }
856
797
  for (const [path, rule] of ruleIndex) {
@@ -864,7 +805,7 @@ function collectAttributeEntries(eventsByPath) {
864
805
  if (!attributes)
865
806
  return;
866
807
  for (const [key, entry] of attributes.entries()) {
867
- const path = `${basePath}@${key}`;
808
+ const path = appendAttributePath(basePath, key);
868
809
  entries.push({ path, span: entry.span });
869
810
  visit(path, entry.attributes);
870
811
  }
@@ -974,7 +915,7 @@ function checkAttributeConstraints(ruleIndex, eventsByPath, datatypeRules, ctx)
974
915
  function validateAttributeMap(basePath, attributes, constraints, datatypeRules, ctx) {
975
916
  const requiredAttributes = constraints.attributes ?? {};
976
917
  for (const [key, childConstraints] of Object.entries(requiredAttributes)) {
977
- const childPath = `${basePath}@${key}`;
918
+ const childPath = appendAttributePath(basePath, key);
978
919
  const entry = attributes?.get(key);
979
920
  if (childConstraints.required === true && !entry) {
980
921
  emitError(ctx, createDiag(childPath, null, `Missing required field: ${childPath}`, ErrorCodes.MISSING_REQUIRED_FIELD));
@@ -989,7 +930,7 @@ function validateAttributeMap(basePath, attributes, constraints, datatypeRules,
989
930
  for (const key of attributes.keys()) {
990
931
  if (allowed.has(key))
991
932
  continue;
992
- const childPath = `${basePath}@${key}`;
933
+ const childPath = appendAttributePath(basePath, key);
993
934
  emitError(ctx, createDiag(childPath, attributes.get(key)?.span ?? null, `Attribute '${childPath}' is not allowed by closed attribute constraints`, ErrorCodes.UNEXPECTED_ATTRIBUTE_ENTRY));
994
935
  }
995
936
  }
@@ -1062,14 +1003,6 @@ function validateAttributeEntry(path, entry, constraints, datatypeRules, ctx) {
1062
1003
  validateAttributeMap(path, entry.attributes, effectiveConstraints, datatypeRules, ctx);
1063
1004
  }
1064
1005
  }
1065
- function datatypeBase(datatype) {
1066
- const genericIdx = datatype.indexOf('<');
1067
- const separatorIdx = datatype.indexOf('[');
1068
- const endIdx = [genericIdx, separatorIdx]
1069
- .filter((idx) => idx >= 0)
1070
- .reduce((min, idx) => Math.min(min, idx), datatype.length);
1071
- return datatype.slice(0, endIdx);
1072
- }
1073
1006
  function mergeDatatypeRuleConstraints(constraints, datatype, datatypeRules) {
1074
1007
  if (!datatype || !datatypeRules)
1075
1008
  return constraints;
@@ -1203,26 +1136,18 @@ function radixConstraintMatches(datatype, raw, constraints) {
1203
1136
  return false;
1204
1137
  return firstInvalidRadixDigit(raw, constraints.radix) === null;
1205
1138
  }
1206
- function declaredRadixFromDatatype(datatype) {
1207
- if (datatype === undefined)
1208
- return null;
1209
- const match = /^radix(?:\[(\d+)\]|(\d+))$/i.exec(datatype.trim());
1210
- if (!match)
1211
- return null;
1212
- const value = Number(match[1] ?? match[2]);
1213
- return Number.isInteger(value) && value >= 0 ? value : null;
1214
- }
1215
1139
  function isStringType(type) {
1216
1140
  return type === 'StringLiteral'
1217
1141
  || type === 'TrimtickLiteral'
1218
1142
  || type === 'TrimtickStringLiteral'
1219
1143
  || type === 'SeparatorLiteral'
1144
+ || type === 'SansaAddressLiteral'
1220
1145
  || type === 'NullLiteral'
1221
1146
  || type === 'EncodingLiteral'
1222
1147
  || type === 'DateLiteral'
1223
1148
  || type === 'TimeLiteral'
1224
1149
  || type === 'DateTimeLiteral'
1225
- || type === 'ZRUTDateTimeLiteral';
1150
+ || type === 'WTCDateTimeLiteral';
1226
1151
  }
1227
1152
  function countFormDigits(type, raw) {
1228
1153
  if (type === 'NumberLiteral')