@altopelago/aeos-core 0.9.1 → 0.9.2
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/dist/rules/numericForm.d.ts +1 -0
- package/dist/rules/numericForm.d.ts.map +1 -1
- package/dist/rules/numericForm.js +21 -3
- package/dist/rules/numericForm.js.map +1 -1
- package/dist/rules/referenceForm.d.ts.map +1 -1
- package/dist/rules/referenceForm.js +5 -1
- package/dist/rules/referenceForm.js.map +1 -1
- package/dist/rules/schemaIndex.d.ts.map +1 -1
- package/dist/rules/schemaIndex.js +186 -6
- package/dist/rules/schemaIndex.js.map +1 -1
- package/dist/rules/stringForm.d.ts +3 -2
- package/dist/rules/stringForm.d.ts.map +1 -1
- package/dist/rules/stringForm.js +35 -24
- package/dist/rules/stringForm.js.map +1 -1
- package/dist/types/schema.d.ts +17 -2
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/types/schema.js +1 -0
- package/dist/types/schema.js.map +1 -1
- package/dist/validate.d.ts +3 -0
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +231 -16
- package/dist/validate.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,sBAAuB,SAAQ,aAAa;CAAG;AAEhE,MAAM,WAAW,aAAa;IAC1B,6BAA6B;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAE5B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAEvB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAE3C,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAE5B,gEAAgE;IAChE,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAElC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAE7B,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzC,mCAAmC;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAEnD,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IAEpD,uDAAuD;IACvD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC;IAEzD,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/types/schema.js
CHANGED
package/dist/types/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/types/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoJH;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAwB,IAAI,GAAG,CAAC;IAC9D,UAAU;IACV,MAAM;IACN,QAAQ;IACR,UAAU;IACV,gBAAgB;IAChB,WAAW;IACX,YAAY;IACZ,aAAa;IACb,aAAa;IACb,WAAW;IACX,gBAAgB;IAChB,0BAA0B;IAC1B,wBAAwB;IACxB,SAAS;IACT,cAAc;IACd,cAAc;IACd,cAAc;IACd,MAAM;IACN,YAAY;IACZ,YAAY;IACZ,OAAO;IACP,yBAAyB;IACzB,WAAW;IACX,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,SAAS;IACT,UAAU;IACV,YAAY;IACZ,mBAAmB;CACtB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAoC;IACzE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
|
package/dist/validate.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import type { AES } from './types/aes.js';
|
|
7
7
|
import type { SchemaV1 } from './types/schema.js';
|
|
8
8
|
import type { ResultEnvelope } from './types/envelope.js';
|
|
9
|
+
import type { ResourcePolicyV1 } from './types/schema.js';
|
|
9
10
|
/**
|
|
10
11
|
* Validation options
|
|
11
12
|
*/
|
|
@@ -21,6 +22,8 @@ export interface ValidateOptions {
|
|
|
21
22
|
* - error: emit error
|
|
22
23
|
*/
|
|
23
24
|
readonly trailingSeparatorDelimiterPolicy?: 'off' | 'warn' | 'error';
|
|
25
|
+
/** Optional consumer-controlled validation resource limits. */
|
|
26
|
+
readonly resourcePolicy?: ResourcePolicyV1;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Validate an AES against a schema.
|
package/dist/validate.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/validate.js
CHANGED
|
@@ -12,7 +12,7 @@ import { checkPresence } from './rules/presence.js';
|
|
|
12
12
|
import { checkTypes } from './rules/typeCheck.js';
|
|
13
13
|
import { checkReferenceForms } from './rules/referenceForm.js';
|
|
14
14
|
import { checkNumericForm } from './rules/numericForm.js';
|
|
15
|
-
import { checkStringForm, checkPatterns } from './rules/stringForm.js';
|
|
15
|
+
import { checkStringForm, checkPatterns, matchesPortablePattern } from './rules/stringForm.js';
|
|
16
16
|
const TYPE_ALIASES = {
|
|
17
17
|
NumberLiteral: ['NumberLiteral'],
|
|
18
18
|
StringLiteral: ['StringLiteral'],
|
|
@@ -40,6 +40,113 @@ const TYPE_ALIASES = {
|
|
|
40
40
|
function formatQuotedMemberSegment(key) {
|
|
41
41
|
return `.[${JSON.stringify(String(key))}]`;
|
|
42
42
|
}
|
|
43
|
+
const DEFAULT_RESOURCE_POLICY = {
|
|
44
|
+
max_events: 100_000,
|
|
45
|
+
max_rules: 10_000,
|
|
46
|
+
max_any_of_cases: 64,
|
|
47
|
+
max_schema_depth: 64,
|
|
48
|
+
max_path_length: 4_096,
|
|
49
|
+
max_reference_resolution_steps: 64,
|
|
50
|
+
max_selector_expansions: 100_000,
|
|
51
|
+
max_string_length_default: 10_000_000,
|
|
52
|
+
max_container_children_default: 1_000_000,
|
|
53
|
+
};
|
|
54
|
+
function normalizeResourcePolicy(policy, source, ctx) {
|
|
55
|
+
if (policy === undefined)
|
|
56
|
+
return {};
|
|
57
|
+
if (policy === null || typeof policy !== 'object') {
|
|
58
|
+
emitResourceError(ctx, '$', `${source} resource policy must be an object`);
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
const normalized = {};
|
|
62
|
+
for (const key of Object.keys(policy)) {
|
|
63
|
+
if (!(key in DEFAULT_RESOURCE_POLICY)) {
|
|
64
|
+
emitResourceError(ctx, '$', `Unknown ${source} resource policy key: ${String(key)}`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const value = policy[key];
|
|
68
|
+
if (value !== undefined && (typeof value !== 'number' || !Number.isInteger(value) || value < 0)) {
|
|
69
|
+
emitResourceError(ctx, '$', `${source} resource policy ${String(key)} must be a non-negative integer`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (value !== undefined) {
|
|
73
|
+
normalized[key] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return normalized;
|
|
77
|
+
}
|
|
78
|
+
function resolveResourcePolicy(schemaPolicy, optionPolicy, ctx) {
|
|
79
|
+
return {
|
|
80
|
+
...DEFAULT_RESOURCE_POLICY,
|
|
81
|
+
...normalizeResourcePolicy(schemaPolicy, 'schema', ctx),
|
|
82
|
+
...normalizeResourcePolicy(optionPolicy, 'option', ctx),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function emitResourceError(ctx, path, message, span = null) {
|
|
86
|
+
emitError(ctx, createDiag(path, span, message, ErrorCodes.INVALID_SCHEMA_POLICY));
|
|
87
|
+
}
|
|
88
|
+
const STRING_LIKE_VALUE_TYPES = new Set([
|
|
89
|
+
'StringLiteral',
|
|
90
|
+
'TrimtickLiteral',
|
|
91
|
+
'SeparatorLiteral',
|
|
92
|
+
'HexLiteral',
|
|
93
|
+
'EncodingLiteral',
|
|
94
|
+
'NullLiteral',
|
|
95
|
+
'DateLiteral',
|
|
96
|
+
'TimeLiteral',
|
|
97
|
+
'DateTimeLiteral',
|
|
98
|
+
'ZRUTDateTimeLiteral',
|
|
99
|
+
]);
|
|
100
|
+
function stringLikePayloadLength(event) {
|
|
101
|
+
if (!STRING_LIKE_VALUE_TYPES.has(event.type))
|
|
102
|
+
return null;
|
|
103
|
+
const payload = event.value.length > 0 ? event.value : event.raw;
|
|
104
|
+
return payload.length;
|
|
105
|
+
}
|
|
106
|
+
function enforceStringLengthResourceBudget(info, path, policy, ctx) {
|
|
107
|
+
const payloadLength = stringLikePayloadLength(info);
|
|
108
|
+
if (payloadLength !== null && payloadLength > policy.max_string_length_default) {
|
|
109
|
+
emitResourceError(ctx, path, `String-like payload length ${payloadLength} exceeds max_string_length_default ${policy.max_string_length_default}`, info.span);
|
|
110
|
+
}
|
|
111
|
+
for (const [key, attribute] of info.attributes ?? []) {
|
|
112
|
+
enforceStringLengthResourceBudget(attribute, `${path}@${key}`, policy, ctx);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function inspectSchemaResourceShape(schema, policy, ctx) {
|
|
116
|
+
for (const rule of schema.rules) {
|
|
117
|
+
const rulePath = typeof rule.path === 'string' && rule.path.length > 0
|
|
118
|
+
? rule.path
|
|
119
|
+
: typeof rule.selector === 'string' && rule.selector.length > 0
|
|
120
|
+
? rule.selector
|
|
121
|
+
: '$';
|
|
122
|
+
if (rulePath.length > policy.max_path_length) {
|
|
123
|
+
emitResourceError(ctx, rulePath, `Rule path length ${rulePath.length} exceeds max_path_length ${policy.max_path_length}`);
|
|
124
|
+
}
|
|
125
|
+
inspectConstraintResourceShape(rule.constraints, rulePath, 1, policy, ctx);
|
|
126
|
+
}
|
|
127
|
+
for (const [datatype, constraints] of Object.entries(schema.datatype_rules ?? {})) {
|
|
128
|
+
inspectConstraintResourceShape(constraints, `datatype_rules.${datatype}`, 1, policy, ctx);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function inspectConstraintResourceShape(constraints, path, depth, policy, ctx) {
|
|
132
|
+
if (depth > policy.max_schema_depth) {
|
|
133
|
+
emitResourceError(ctx, path, `Schema constraint depth exceeds max_schema_depth ${policy.max_schema_depth}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (constraints.any_of !== undefined) {
|
|
137
|
+
if (constraints.any_of.length > policy.max_any_of_cases) {
|
|
138
|
+
emitResourceError(ctx, path, `any_of case count ${constraints.any_of.length} exceeds max_any_of_cases ${policy.max_any_of_cases}`);
|
|
139
|
+
}
|
|
140
|
+
constraints.any_of.forEach((branch, index) => {
|
|
141
|
+
inspectConstraintResourceShape(branch, `${path}.any_of[${index}]`, depth + 1, policy, ctx);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (constraints.attributes !== undefined) {
|
|
145
|
+
for (const [key, child] of Object.entries(constraints.attributes)) {
|
|
146
|
+
inspectConstraintResourceShape(child, `${path}@${key}`, depth + 1, policy, ctx);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
43
150
|
/**
|
|
44
151
|
* Validate an AES against a schema.
|
|
45
152
|
*
|
|
@@ -70,6 +177,20 @@ export function validate(aes, schema, options = {}) {
|
|
|
70
177
|
// TODO: Phase 9 - Guarantees
|
|
71
178
|
// Phase 1: Envelope plumbing
|
|
72
179
|
const ctx = createDiagContext();
|
|
180
|
+
const resourcePolicy = resolveResourcePolicy(schema.resource_policy, options.resourcePolicy, ctx);
|
|
181
|
+
if (ctx.errors.length > 0) {
|
|
182
|
+
return createFailingEnvelope(ctx.errors, ctx.warnings, {});
|
|
183
|
+
}
|
|
184
|
+
if (aes.length > resourcePolicy.max_events) {
|
|
185
|
+
emitResourceError(ctx, '$', `AES event count ${aes.length} exceeds max_events ${resourcePolicy.max_events}`);
|
|
186
|
+
}
|
|
187
|
+
if (schema.rules.length > resourcePolicy.max_rules) {
|
|
188
|
+
emitResourceError(ctx, '$', `Schema rule count ${schema.rules.length} exceeds max_rules ${resourcePolicy.max_rules}`);
|
|
189
|
+
}
|
|
190
|
+
inspectSchemaResourceShape(schema, resourcePolicy, ctx);
|
|
191
|
+
if (ctx.errors.length > 0) {
|
|
192
|
+
return createFailingEnvelope(ctx.errors, ctx.warnings, {});
|
|
193
|
+
}
|
|
73
194
|
// Phase 3: (moved to run after Phase 2)
|
|
74
195
|
// Helpers: format canonical path (local, no runtime AEON deps)
|
|
75
196
|
function formatCanonicalPath(path) {
|
|
@@ -182,6 +303,9 @@ export function validate(aes, schema, options = {}) {
|
|
|
182
303
|
for (let i = 0; i < aes.length; i++) {
|
|
183
304
|
const event = aes[i];
|
|
184
305
|
const pathStr = formatCanonicalPath(event.path);
|
|
306
|
+
if (pathStr.length > resourcePolicy.max_path_length) {
|
|
307
|
+
emitResourceError(ctx, pathStr, `Path length ${pathStr.length} exceeds max_path_length ${resourcePolicy.max_path_length}`, toTuple(event.span));
|
|
308
|
+
}
|
|
185
309
|
if (Array.isArray(event.path?.segments)) {
|
|
186
310
|
for (const seg of event.path.segments) {
|
|
187
311
|
if (seg?.type === 'index') {
|
|
@@ -217,19 +341,31 @@ export function validate(aes, schema, options = {}) {
|
|
|
217
341
|
if ((event.value.type === 'TupleLiteral' || event.value.type === 'ListLiteral' || event.value.type === 'ListNode')
|
|
218
342
|
&& Array.isArray(event.value.elements)) {
|
|
219
343
|
containerArity.set(pathStr, event.value.elements.length);
|
|
344
|
+
if (event.value.elements.length > resourcePolicy.max_container_children_default) {
|
|
345
|
+
emitResourceError(ctx, pathStr, `Container child count ${event.value.elements.length} exceeds max_container_children_default ${resourcePolicy.max_container_children_default}`, toTuple(event.span));
|
|
346
|
+
}
|
|
220
347
|
hydrateIndexedFallback(pathStr, event.value, toTuple(event.span));
|
|
221
348
|
}
|
|
222
349
|
else if (event.value.type === 'ObjectNode' && Array.isArray(event.value.bindings)) {
|
|
223
350
|
containerArity.set(pathStr, event.value.bindings.length);
|
|
351
|
+
if (event.value.bindings.length > resourcePolicy.max_container_children_default) {
|
|
352
|
+
emitResourceError(ctx, pathStr, `Container child count ${event.value.bindings.length} exceeds max_container_children_default ${resourcePolicy.max_container_children_default}`, toTuple(event.span));
|
|
353
|
+
}
|
|
224
354
|
}
|
|
225
355
|
else if (event.value.type === 'NodeLiteral' && Array.isArray(event.value.children)) {
|
|
226
356
|
containerArity.set(pathStr, event.value.children.length);
|
|
357
|
+
if (event.value.children.length > resourcePolicy.max_container_children_default) {
|
|
358
|
+
emitResourceError(ctx, pathStr, `Container child count ${event.value.children.length} exceeds max_container_children_default ${resourcePolicy.max_container_children_default}`, toTuple(event.span));
|
|
359
|
+
}
|
|
227
360
|
hydrateIndexedFallback(pathStr, event.value, toTuple(event.span));
|
|
228
361
|
}
|
|
229
362
|
}
|
|
230
363
|
}
|
|
231
364
|
// Register index even for first occurrence
|
|
232
365
|
}
|
|
366
|
+
for (const [path, info] of eventsByPath) {
|
|
367
|
+
enforceStringLengthResourceBudget(info, path, resourcePolicy, ctx);
|
|
368
|
+
}
|
|
233
369
|
// Optional separator literal trailing-delimiter policy
|
|
234
370
|
if (trailingSeparatorPolicy !== 'off') {
|
|
235
371
|
for (const event of aes) {
|
|
@@ -254,14 +390,16 @@ export function validate(aes, schema, options = {}) {
|
|
|
254
390
|
}
|
|
255
391
|
// Phase 3: Build rule index from schema (run after baseline invariants)
|
|
256
392
|
const ruleIndex = buildRuleIndex(schema, ctx);
|
|
257
|
-
const
|
|
393
|
+
const selectorExpansionBudget = { count: 0 };
|
|
394
|
+
const expandedRuleIndex = expandWildcardRules(expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolicy, selectorExpansionBudget), eventsByPath, ctx, resourcePolicy, selectorExpansionBudget);
|
|
395
|
+
const effectiveRuleIndex = mergeDatatypeRules(expandedRuleIndex, schema.datatype_rules, eventsByPath);
|
|
258
396
|
// Phase 4: Presence checks (required fields)
|
|
259
397
|
const boundPaths = new Set(seen.keys());
|
|
260
398
|
checkPresence(effectiveRuleIndex, boundPaths, ctx);
|
|
261
399
|
checkWorldPolicy(schema, aes, boundPaths, ctx);
|
|
262
400
|
// Phase 5: Type checks (literal kind)
|
|
263
401
|
checkReferenceForms(schema, effectiveRuleIndex, eventsByPath, ctx);
|
|
264
|
-
const effectiveEventsByPath = resolveReferenceFormEvents(effectiveRuleIndex, eventsByPath);
|
|
402
|
+
const effectiveEventsByPath = resolveReferenceFormEvents(effectiveRuleIndex, eventsByPath, resourcePolicy, ctx);
|
|
265
403
|
const selectedRuleIndex = selectAnyOfRules(effectiveRuleIndex, effectiveEventsByPath, ctx);
|
|
266
404
|
checkTypes(selectedRuleIndex, effectiveEventsByPath, ctx);
|
|
267
405
|
// Phase 5b: core v1 arity/cardinality checks for tuple/list/node containers
|
|
@@ -380,7 +518,7 @@ function checkWorldPolicy(schema, aes, boundPaths, ctx) {
|
|
|
380
518
|
emitError(ctx, createDiag(path, toTupleLocal(event.span), `Binding '${path}' is not allowed by closed-world schema`, ErrorCodes.UNEXPECTED_BINDING));
|
|
381
519
|
}
|
|
382
520
|
}
|
|
383
|
-
function resolveReferenceFormEvents(ruleIndex, eventsByPath) {
|
|
521
|
+
function resolveReferenceFormEvents(ruleIndex, eventsByPath, resourcePolicy, ctx) {
|
|
384
522
|
const resolved = new Map(eventsByPath);
|
|
385
523
|
for (const [path, rule] of ruleIndex.entries()) {
|
|
386
524
|
if (rule.constraints.resolve_reference_form !== true)
|
|
@@ -388,8 +526,12 @@ function resolveReferenceFormEvents(ruleIndex, eventsByPath) {
|
|
|
388
526
|
const event = eventsByPath.get(path);
|
|
389
527
|
if (!event || !isReferenceType(event.type) || !event.referencePath)
|
|
390
528
|
continue;
|
|
391
|
-
const
|
|
529
|
+
const resolutionState = { exhausted: false };
|
|
530
|
+
const terminal = resolveTerminalReferenceEvent(event, eventsByPath, new Set(), resourcePolicy.max_reference_resolution_steps, resolutionState);
|
|
392
531
|
if (!terminal) {
|
|
532
|
+
if (resolutionState.exhausted) {
|
|
533
|
+
emitResourceError(ctx, path, `Reference resolution exceeded max_reference_resolution_steps ${resourcePolicy.max_reference_resolution_steps}`, event.span);
|
|
534
|
+
}
|
|
393
535
|
resolved.delete(path);
|
|
394
536
|
continue;
|
|
395
537
|
}
|
|
@@ -400,7 +542,7 @@ function resolveReferenceFormEvents(ruleIndex, eventsByPath) {
|
|
|
400
542
|
}
|
|
401
543
|
return resolved;
|
|
402
544
|
}
|
|
403
|
-
function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx) {
|
|
545
|
+
function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx, resourcePolicy, expansionBudget) {
|
|
404
546
|
const expanded = new Map(ruleIndex);
|
|
405
547
|
for (const rule of schema.rules) {
|
|
406
548
|
if (typeof rule.selector !== 'string' || rule.selector.length === 0)
|
|
@@ -412,6 +554,11 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx) {
|
|
|
412
554
|
if (!matchesSelectorPath(actualPath, rule.selector))
|
|
413
555
|
continue;
|
|
414
556
|
matched = true;
|
|
557
|
+
expansionBudget.count += 1;
|
|
558
|
+
if (expansionBudget.count > resourcePolicy.max_selector_expansions) {
|
|
559
|
+
emitResourceError(ctx, rule.selector, `Selector expansion count exceeds max_selector_expansions ${resourcePolicy.max_selector_expansions}`);
|
|
560
|
+
return expanded;
|
|
561
|
+
}
|
|
415
562
|
if (!expanded.has(actualPath)) {
|
|
416
563
|
expanded.set(actualPath, { ...rule, path: actualPath });
|
|
417
564
|
}
|
|
@@ -422,7 +569,7 @@ function expandSelectorRules(ruleIndex, schema, eventsByPath, ctx) {
|
|
|
422
569
|
}
|
|
423
570
|
return expanded;
|
|
424
571
|
}
|
|
425
|
-
function expandWildcardRules(ruleIndex, eventsByPath) {
|
|
572
|
+
function expandWildcardRules(ruleIndex, eventsByPath, ctx, resourcePolicy, expansionBudget) {
|
|
426
573
|
const expanded = new Map(ruleIndex);
|
|
427
574
|
for (const [path, rule] of ruleIndex.entries()) {
|
|
428
575
|
if (!path.includes('[*]'))
|
|
@@ -430,6 +577,11 @@ function expandWildcardRules(ruleIndex, eventsByPath) {
|
|
|
430
577
|
expanded.delete(path);
|
|
431
578
|
for (const actualPath of eventsByPath.keys()) {
|
|
432
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
|
+
}
|
|
433
585
|
expanded.set(actualPath, { ...rule, path: actualPath });
|
|
434
586
|
}
|
|
435
587
|
}
|
|
@@ -459,6 +611,9 @@ function withoutAnyOf(constraints) {
|
|
|
459
611
|
const { any_of: _anyOf, ...rest } = constraints;
|
|
460
612
|
return rest;
|
|
461
613
|
}
|
|
614
|
+
function patternMatches(pattern, value) {
|
|
615
|
+
return matchesPortablePattern(pattern, value);
|
|
616
|
+
}
|
|
462
617
|
function constraintBranchMatchesEvent(constraints, event) {
|
|
463
618
|
if (constraints.type_is !== undefined) {
|
|
464
619
|
const containerOk = constraints.type_is === 'list'
|
|
@@ -492,7 +647,7 @@ function constraintBranchMatchesEvent(constraints, event) {
|
|
|
492
647
|
return false;
|
|
493
648
|
if (constraints.max_length !== undefined && valueLength > constraints.max_length)
|
|
494
649
|
return false;
|
|
495
|
-
if (
|
|
650
|
+
if (!patternMatches(constraints.pattern, event.value))
|
|
496
651
|
return false;
|
|
497
652
|
}
|
|
498
653
|
if (hasDigitFormConstraints(constraints) && isDigitFormLiteral(event.type)) {
|
|
@@ -503,15 +658,19 @@ function constraintBranchMatchesEvent(constraints, event) {
|
|
|
503
658
|
return false;
|
|
504
659
|
if (constraints.max_digits !== undefined && digitCount > constraints.max_digits)
|
|
505
660
|
return false;
|
|
506
|
-
if (event.type === 'RadixLiteral' && constraints.radix !== undefined &&
|
|
661
|
+
if (event.type === 'RadixLiteral' && constraints.radix !== undefined && !radixConstraintMatches(event.datatype, event.raw, constraints))
|
|
507
662
|
return false;
|
|
508
663
|
}
|
|
509
664
|
return true;
|
|
510
665
|
}
|
|
511
|
-
function resolveTerminalReferenceEvent(event, eventsByPath, activePaths) {
|
|
666
|
+
function resolveTerminalReferenceEvent(event, eventsByPath, activePaths, remainingSteps, state) {
|
|
512
667
|
if (!isReferenceType(event.type) || !event.referencePath) {
|
|
513
668
|
return event;
|
|
514
669
|
}
|
|
670
|
+
if (remainingSteps <= 0) {
|
|
671
|
+
state.exhausted = true;
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
515
674
|
const targetPath = formatReferenceLookupPath(event.referencePath);
|
|
516
675
|
if (activePaths.has(targetPath)) {
|
|
517
676
|
return null;
|
|
@@ -522,7 +681,7 @@ function resolveTerminalReferenceEvent(event, eventsByPath, activePaths) {
|
|
|
522
681
|
}
|
|
523
682
|
activePaths.add(targetPath);
|
|
524
683
|
const resolved = isReferenceType(target.type)
|
|
525
|
-
? resolveTerminalReferenceEvent(target, eventsByPath, activePaths)
|
|
684
|
+
? resolveTerminalReferenceEvent(target, eventsByPath, activePaths, remainingSteps - 1, state)
|
|
526
685
|
: target;
|
|
527
686
|
activePaths.delete(targetPath);
|
|
528
687
|
return resolved;
|
|
@@ -780,6 +939,28 @@ function checkDatatypeRules(datatypeRules, eventsByPath, ctx) {
|
|
|
780
939
|
}
|
|
781
940
|
}
|
|
782
941
|
}
|
|
942
|
+
function mergeDatatypeRules(ruleIndex, datatypeRules, eventsByPath) {
|
|
943
|
+
if (!datatypeRules)
|
|
944
|
+
return ruleIndex;
|
|
945
|
+
const merged = new Map();
|
|
946
|
+
for (const [path, rule] of ruleIndex.entries()) {
|
|
947
|
+
merged.set(path, { ...rule, constraints: { ...rule.constraints } });
|
|
948
|
+
}
|
|
949
|
+
for (const [path, event] of eventsByPath.entries()) {
|
|
950
|
+
if (!event.datatype)
|
|
951
|
+
continue;
|
|
952
|
+
const datatypeRule = datatypeRules[datatypeBase(event.datatype).toLowerCase()];
|
|
953
|
+
if (!datatypeRule)
|
|
954
|
+
continue;
|
|
955
|
+
const existing = merged.get(path);
|
|
956
|
+
const constraints = existing?.constraints ?? {};
|
|
957
|
+
merged.set(path, {
|
|
958
|
+
path,
|
|
959
|
+
constraints: { ...datatypeRule, ...constraints },
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return merged;
|
|
963
|
+
}
|
|
783
964
|
function checkAttributeConstraints(ruleIndex, eventsByPath, datatypeRules, ctx) {
|
|
784
965
|
for (const [path, rule] of ruleIndex) {
|
|
785
966
|
if (!rule.constraints.attributes && rule.constraints.closed_attributes !== true)
|
|
@@ -854,20 +1035,26 @@ function validateAttributeEntry(path, entry, constraints, datatypeRules, ctx) {
|
|
|
854
1035
|
emitError(ctx, createDiag(path, entry.span, `Numeric form violation: expected max ${effectiveConstraints.max_digits} digits, got ${digitCount}`, ErrorCodes.NUMERIC_FORM_VIOLATION));
|
|
855
1036
|
}
|
|
856
1037
|
if (entry.type === 'RadixLiteral' && effectiveConstraints.radix !== undefined) {
|
|
1038
|
+
const declaredRadix = declaredRadixFromDatatype(entry.datatype);
|
|
1039
|
+
if ((declaredRadix === null && effectiveConstraints.allow_unspecified_radix !== true)
|
|
1040
|
+
|| (declaredRadix !== null && declaredRadix !== effectiveConstraints.radix)) {
|
|
1041
|
+
emitError(ctx, createDiag(path, entry.span, `Numeric form violation: expected declared radix ${effectiveConstraints.radix}`, ErrorCodes.NUMERIC_FORM_VIOLATION));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
857
1044
|
const invalidDigit = firstInvalidRadixDigit(entry.raw, effectiveConstraints.radix);
|
|
858
1045
|
if (invalidDigit !== null) {
|
|
859
1046
|
emitError(ctx, createDiag(path, entry.span, `Numeric form violation: radix literal digit '${invalidDigit}' is outside radix ${effectiveConstraints.radix}`, ErrorCodes.NUMERIC_FORM_VIOLATION));
|
|
860
1047
|
}
|
|
861
1048
|
}
|
|
862
1049
|
}
|
|
863
|
-
if (entry.type
|
|
1050
|
+
if (isStringType(entry.type)) {
|
|
864
1051
|
if (effectiveConstraints.min_length !== undefined && entry.value.length < effectiveConstraints.min_length) {
|
|
865
1052
|
emitError(ctx, createDiag(path, entry.span, `String length violation: expected min length ${effectiveConstraints.min_length}, got ${entry.value.length}`, ErrorCodes.STRING_LENGTH_VIOLATION));
|
|
866
1053
|
}
|
|
867
1054
|
if (effectiveConstraints.max_length !== undefined && entry.value.length > effectiveConstraints.max_length) {
|
|
868
1055
|
emitError(ctx, createDiag(path, entry.span, `String length violation: expected max length ${effectiveConstraints.max_length}, got ${entry.value.length}`, ErrorCodes.STRING_LENGTH_VIOLATION));
|
|
869
1056
|
}
|
|
870
|
-
if (effectiveConstraints.pattern !== undefined && !(
|
|
1057
|
+
if (effectiveConstraints.pattern !== undefined && !patternMatches(effectiveConstraints.pattern, entry.value)) {
|
|
871
1058
|
emitError(ctx, createDiag(path, entry.span, `Pattern mismatch: value does not match ${effectiveConstraints.pattern}`, ErrorCodes.PATTERN_MISMATCH));
|
|
872
1059
|
}
|
|
873
1060
|
}
|
|
@@ -1004,10 +1191,38 @@ function hasDigitFormConstraints(constraints) {
|
|
|
1004
1191
|
return constraints.sign !== undefined || constraints.min_digits !== undefined || constraints.max_digits !== undefined || constraints.radix !== undefined;
|
|
1005
1192
|
}
|
|
1006
1193
|
function isDigitFormLiteral(type) {
|
|
1007
|
-
return type === 'NumberLiteral' || type === 'HexLiteral' || type === 'RadixLiteral'
|
|
1194
|
+
return type === 'NumberLiteral' || type === 'HexLiteral' || type === 'RadixLiteral';
|
|
1195
|
+
}
|
|
1196
|
+
function radixConstraintMatches(datatype, raw, constraints) {
|
|
1197
|
+
if (constraints.radix === undefined)
|
|
1198
|
+
return true;
|
|
1199
|
+
const declaredRadix = declaredRadixFromDatatype(datatype);
|
|
1200
|
+
if (declaredRadix === null && constraints.allow_unspecified_radix !== true)
|
|
1201
|
+
return false;
|
|
1202
|
+
if (declaredRadix !== null && declaredRadix !== constraints.radix)
|
|
1203
|
+
return false;
|
|
1204
|
+
return firstInvalidRadixDigit(raw, constraints.radix) === null;
|
|
1205
|
+
}
|
|
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;
|
|
1008
1214
|
}
|
|
1009
1215
|
function isStringType(type) {
|
|
1010
|
-
return type === 'StringLiteral'
|
|
1216
|
+
return type === 'StringLiteral'
|
|
1217
|
+
|| type === 'TrimtickLiteral'
|
|
1218
|
+
|| type === 'TrimtickStringLiteral'
|
|
1219
|
+
|| type === 'SeparatorLiteral'
|
|
1220
|
+
|| type === 'NullLiteral'
|
|
1221
|
+
|| type === 'EncodingLiteral'
|
|
1222
|
+
|| type === 'DateLiteral'
|
|
1223
|
+
|| type === 'TimeLiteral'
|
|
1224
|
+
|| type === 'DateTimeLiteral'
|
|
1225
|
+
|| type === 'ZRUTDateTimeLiteral';
|
|
1011
1226
|
}
|
|
1012
1227
|
function countFormDigits(type, raw) {
|
|
1013
1228
|
if (type === 'NumberLiteral')
|
|
@@ -1018,7 +1233,7 @@ function countFormDigits(type, raw) {
|
|
|
1018
1233
|
.replace(/_/g, '');
|
|
1019
1234
|
let count = 0;
|
|
1020
1235
|
for (const char of body) {
|
|
1021
|
-
if ((char >= '0' && char <= '9') || (
|
|
1236
|
+
if ((char >= '0' && char <= '9') || ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char === '&' || char === '!')) {
|
|
1022
1237
|
count++;
|
|
1023
1238
|
}
|
|
1024
1239
|
}
|