@callcium/sdk 0.12.0 → 0.13.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/signature.ts CHANGED
@@ -31,14 +31,14 @@ function isAlphanum(char: number): boolean {
31
31
  /**
32
32
  * Parse an ABI function signature into its selector and types.
33
33
  *
34
- * Strict mode: no space, tab, LF or CR; name must match `[A-Za-z_][A-Za-z0-9_]*`; structure must be `name(types)` with
35
- * the final `)` as the last character.
34
+ * Strict mode: no space, tab, LF or CR. The name must match `[A-Za-z_][A-Za-z0-9_]*`. The structure must be
35
+ * `name(types)` with the final `)` as the last character.
36
36
  *
37
- * @param signature - A function signature string, e.g. `"transfer(address,uint256)"`.
37
+ * @param signature - A function signature string, for example `"transfer(address,uint256)"`.
38
38
  * @returns The 4-byte selector and the comma-separated types string.
39
39
  * @throws {CallciumError} with code `"MALFORMED_SIGNATURE"`, `"SIGNATURE_CONTAINS_WHITESPACE"`, or
40
- * `"INVALID_FUNCTION_NAME"` if the signature is malformed. The types between the parentheses are returned unread;
41
- * whether they name types is for the type parser to say.
40
+ * `"INVALID_FUNCTION_NAME"` if the signature is malformed. The types between the parentheses are returned unread.
41
+ * The type parser determines whether the types string names types.
42
42
  */
43
43
  function parse(signature: string): ParsedSignature {
44
44
  if (signature.length < 3) {
@@ -68,12 +68,8 @@ function parse(signature: string): ParsedSignature {
68
68
  throw new CallciumError("MALFORMED_SIGNATURE", `Invalid signature: must end with ")", got "${signature}"`);
69
69
  }
70
70
 
71
- if (openParen === 0) {
72
- throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must not be empty");
73
- }
74
-
75
- // Validate function name: [A-Za-z_][A-Za-z0-9_]*. Every character outside ASCII carries a code unit above the
76
- // alphanumeric ranges, so the same test rejects it.
71
+ // Validate function name: [A-Za-z_][A-Za-z0-9_]*. An empty name leaves `(` first, which fails the first-character
72
+ // test; every character outside ASCII carries a code unit above the alphanumeric ranges and fails the same tests.
77
73
  const firstChar = signature.charCodeAt(0);
78
74
  if (!isAlpha(firstChar) && firstChar !== 0x5f) {
79
75
  throw new CallciumError("INVALID_FUNCTION_NAME", "Function name must start with a letter or underscore");
package/src/type-desc.ts CHANGED
@@ -42,11 +42,11 @@ export function function_(): Uint8Array {
42
42
  /**
43
43
  * Build a descriptor node for `uintN`.
44
44
  * @param bits - Bit width, 8–256 in steps of 8.
45
- * @throws {CallciumError} With code `UNKNOWN_TYPE` if bits is out of range or not a multiple of 8.
45
+ * @throws {CallciumError} With code `INVALID_UINT_BITS` if bits is out of range or not a multiple of 8.
46
46
  */
47
47
  export function uintN(bits: number): Uint8Array {
48
48
  if (bits < 8 || bits > 256 || bits % 8 !== 0) {
49
- throw new CallciumError("UNKNOWN_TYPE", `Invalid uintN bits: ${bits}. Must be 8–256 in steps of 8`);
49
+ throw new CallciumError("INVALID_UINT_BITS", `Invalid uint width ${bits}`);
50
50
  }
51
51
  return new Uint8Array([TypeCode.UINT_MIN + (bits / 8 - 1)]);
52
52
  }
@@ -54,11 +54,11 @@ export function uintN(bits: number): Uint8Array {
54
54
  /**
55
55
  * Build a descriptor node for `intN`.
56
56
  * @param bits - Bit width, 8–256 in steps of 8.
57
- * @throws {CallciumError} With code `UNKNOWN_TYPE` if bits is out of range or not a multiple of 8.
57
+ * @throws {CallciumError} With code `INVALID_INT_BITS` if bits is out of range or not a multiple of 8.
58
58
  */
59
59
  export function intN(bits: number): Uint8Array {
60
60
  if (bits < 8 || bits > 256 || bits % 8 !== 0) {
61
- throw new CallciumError("UNKNOWN_TYPE", `Invalid intN bits: ${bits}. Must be 8–256 in steps of 8`);
61
+ throw new CallciumError("INVALID_INT_BITS", `Invalid int width ${bits}`);
62
62
  }
63
63
  return new Uint8Array([TypeCode.INT_MIN + (bits / 8 - 1)]);
64
64
  }
@@ -76,11 +76,11 @@ export function string_(): Uint8Array {
76
76
  /**
77
77
  * Build a descriptor node for `bytesN`.
78
78
  * @param length - Byte width, 1–32.
79
- * @throws {CallciumError} With code `UNKNOWN_TYPE` if length is out of range.
79
+ * @throws {CallciumError} With code `INVALID_BYTES_LENGTH` if length is out of range.
80
80
  */
81
81
  export function bytesN(length: number): Uint8Array {
82
82
  if (length < 1 || length > 32) {
83
- throw new CallciumError("UNKNOWN_TYPE", `Invalid bytesN size: ${length}. Must be 1–32`);
83
+ throw new CallciumError("INVALID_BYTES_LENGTH", `Invalid bytes length ${length}`);
84
84
  }
85
85
  return new Uint8Array([TypeCode.FIXED_BYTES_MIN + (length - 1)]);
86
86
  }
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { ComparisonCode, QuantifierCode, Scope, ScopeCode, Source } from "./constants";
2
+
1
3
  ///////////////////////////////////////////////////////////////////////////
2
4
  // Primitives
3
5
  ///////////////////////////////////////////////////////////////////////////
@@ -11,6 +13,9 @@ export type Address = `0x${string}`;
11
13
  /** Byte range in a source blob. */
12
14
  export type Span = { start: number; end: number };
13
15
 
16
+ /** A decoded value with its byte position in the source blob. */
17
+ export type Field<T> = { value: T; span: Span };
18
+
14
19
  ///////////////////////////////////////////////////////////////////////////
15
20
  // Enforce
16
21
  ///////////////////////////////////////////////////////////////////////////
@@ -51,51 +56,54 @@ export type SelectorMismatchViolation = {
51
56
  };
52
57
 
53
58
  /**
54
- * A rule referenced a context property not supplied in the execution context — a context-scoped subject, or a context
55
- * reference operand on either scope.
59
+ * A test read a context property not supplied in the execution context — a context test's subject, or a context
60
+ * operand on either kind of test.
61
+ *
62
+ * `group` and `rule` locate the root rule. A test in a loop body adds `bodyRule` and `elementIndex`.
56
63
  */
57
64
  export type MissingContextViolation = {
58
65
  code: "MISSING_CONTEXT";
59
66
  group: number;
60
67
  rule: number;
61
- scope: number;
68
+ scope: ScopeCode;
69
+ /** The test's path: a context test's property ID, or a calldata test's steps (in a loop body, from the element). */
62
70
  path: Hex;
63
71
  /** Declared type of the missing context property. */
64
72
  typeCode: number;
65
- /** Operator code with the `Op.NOT` bit intact; absent for a context-scoped subject. */
66
- opCode?: number;
67
- /** Operator payload; absent for a context-scoped subject. */
68
- operandData?: Hex;
73
+ /** The test's operator, absent for a context test's subject. */
74
+ operator?: DecodedOperator;
75
+ /** Position of the test in the loop body, absent for a root test. */
76
+ bodyRule?: number;
77
+ /** Position of the element the body test read, absent for a root test. */
78
+ elementIndex?: number;
69
79
  };
70
80
 
71
81
  /**
72
- * A rule's operator returned false against the loaded value.
73
- *
74
- * Field combinations:
75
- * - `resolvedValue` present, `elementIndex` absent — scalar leaf or context value that failed the operator.
76
- * - `resolvedValue` present, `elementIndex` present — universal-quantifier per-element failure.
77
- * - `resolvedValue` absent, `elementIndex` absent — existential-aggregate failure (no element satisfied).
82
+ * A test's operator returned false against the loaded value.
78
83
  *
79
- * For length operators, `resolvedValue` is the length as a 32-byte word.
84
+ * `resolvedValue` is the value the operator compared: a word, or for length operators the length as a 32-byte word.
85
+ * `group` and `rule` locate the root rule. A test in a loop body adds `bodyRule` and `elementIndex`.
80
86
  */
81
87
  export type ValueMismatchViolation = {
82
88
  code: "VALUE_MISMATCH";
83
89
  group: number;
84
90
  rule: number;
85
- scope: number;
91
+ scope: ScopeCode;
92
+ /** The test's path: a context test's property ID, or a calldata test's steps (in a loop body, from the element). */
86
93
  path: Hex;
87
- /** Operator code with the `Op.NOT` bit intact. */
88
- opCode: number;
89
- /** Full untruncated operand bytes declared by the rule. */
90
- operandData: Hex;
91
- /** Type code of the failing value's leaf. */
94
+ /** The test's operator. */
95
+ operator: DecodedOperator;
96
+ /** Type code of the failing value. */
92
97
  typeCode: number;
93
- resolvedValue?: Hex;
98
+ resolvedValue: Hex;
94
99
  /**
95
- * Resolved runtime value of a reference operand as a 32-byte word; present when the operator's operand names a
96
- * reference resolved at enforcement, not a literal.
100
+ * Resolved runtime value of a context operand as a 32-byte word, present when the operator's operand names a context
101
+ * property, not a constant.
97
102
  */
98
103
  resolvedOperand?: Hex;
104
+ /** Position of the test in the loop body, absent for a root test. */
105
+ bodyRule?: number;
106
+ /** Position of the element the body test read, absent for a root test. */
99
107
  elementIndex?: number;
100
108
  };
101
109
 
@@ -107,19 +115,21 @@ type CalldataNavigationVariant<C extends NavigationViolationCode> = {
107
115
  code: C;
108
116
  group: number;
109
117
  rule: number;
110
- scope: number;
118
+ scope: ScopeCode;
111
119
  path: Hex;
112
- opCode?: number;
113
- operandData?: Hex;
120
+ operator?: DecodedOperator;
114
121
  typeCode?: number;
122
+ bodyRule?: number;
115
123
  elementIndex?: number;
116
124
  };
117
125
 
118
126
  /**
119
- * Calldata structure prevented the rule from being evaluated.
127
+ * Calldata structure prevented a rule from being evaluated.
120
128
  *
121
- * The operator was never applied; `opCode`, `operandData`, `typeCode`, and `elementIndex` are diagnostic context
122
- * describing the failing site, not a constraint claim.
129
+ * The operator was never applied. `operator`, `typeCode`, `bodyRule` and `elementIndex` are diagnostic context
130
+ * describing the failing site, not a constraint claim. A failure resolving a loop's array carries the loop
131
+ * rule's scope and array path and no operator. A failure resolving an element adds `elementIndex`. A failure in a body
132
+ * test adds the test's fields and `bodyRule`.
123
133
  *
124
134
  * A union of per-code variants, narrowable with `Extract<Violation, { code: "..." }>`.
125
135
  */
@@ -127,12 +137,13 @@ export type CalldataNavigationViolation =
127
137
  | CalldataNavigationVariant<"CALLDATA_OUT_OF_BOUNDS">
128
138
  | CalldataNavigationVariant<"ARRAY_INDEX_OUT_OF_BOUNDS">;
129
139
 
130
- /** A quantified array exceeded `PolicyFormat.MAX_QUANTIFIED_ARRAY_LENGTH`. */
140
+ /** A loop's array exceeded `MAX_QUANTIFIED_ARRAY_LENGTH`. */
131
141
  export type QuantifierLimitExceededViolation = {
132
142
  code: "QUANTIFIER_LIMIT_EXCEEDED";
133
143
  group: number;
134
144
  rule: number;
135
- scope: number;
145
+ scope: ScopeCode;
146
+ /** The loop's array path. */
136
147
  path: Hex;
137
148
  /** Hex-encoded element count of the offending array. */
138
149
  resolvedValue: Hex;
@@ -147,30 +158,34 @@ export type NonCanonicalValueViolation = {
147
158
  code: "NON_CANONICAL_VALUE";
148
159
  group: number;
149
160
  rule: number;
150
- scope: number;
161
+ scope: ScopeCode;
151
162
  path: Hex;
152
163
  /** Type code of the target whose encoding was violated. */
153
164
  typeCode: number;
154
165
  /** Raw 32-byte word as it appeared in calldata, before any masking. */
155
166
  resolvedValue: Hex;
156
- opCode?: number;
157
- operandData?: Hex;
167
+ /** The test's operator. */
168
+ operator: DecodedOperator;
169
+ /** Position of the test in the loop body, absent for a root test. */
170
+ bodyRule?: number;
171
+ /** Position of the element the body test read, absent for a root test. */
158
172
  elementIndex?: number;
159
173
  };
160
174
 
161
- /** An `ANY` quantifier was applied to an empty array. */
162
- export type QuantifierEmptyArrayViolation = {
163
- code: "QUANTIFIER_EMPTY_ARRAY";
175
+ /** No element satisfied an `ANY` loop's body, the empty array included. No element and no test is to blame. */
176
+ export type QuantifierNoMatchViolation = {
177
+ code: "QUANTIFIER_NO_MATCH";
164
178
  group: number;
165
179
  rule: number;
166
- scope: number;
180
+ scope: ScopeCode;
181
+ /** The loop's array path. */
167
182
  path: Hex;
168
183
  };
169
184
 
170
185
  /**
171
186
  * Structured details of a single rule failure during enforcement.
172
187
  *
173
- * Carries semantic data only; no message strings. Discriminate on `code` to narrow to the matching variant.
188
+ * Carries semantic data only, with no message strings. Discriminate on `code` to narrow to the matching variant.
174
189
  */
175
190
  export type Violation =
176
191
  | MissingSelectorViolation
@@ -180,66 +195,188 @@ export type Violation =
180
195
  | CalldataNavigationViolation
181
196
  | NonCanonicalValueViolation
182
197
  | QuantifierLimitExceededViolation
183
- | QuantifierEmptyArrayViolation;
198
+ | QuantifierNoMatchViolation;
184
199
 
185
200
  /** Machine-readable reason code for an enforcement violation. Derived from `Violation` to prevent drift. */
186
201
  export type ViolationCode = Violation["code"];
187
202
 
188
203
  ///////////////////////////////////////////////////////////////////////////
189
- // Canonical policy types
204
+ // Policy types
190
205
  ///////////////////////////////////////////////////////////////////////////
191
206
 
192
- /** Canonical structured representation of a policy. */
207
+ /** A test or a context test of a loop body, or of a group. */
208
+ export type BodyRule = {
209
+ scope: typeof Scope.CONTEXT | typeof Scope.CALLDATA;
210
+ /** BE16-encoded path from the starting point. A context test's path is its property ID. */
211
+ path: Hex;
212
+ /** The hint a blob carries for the path, absent for a context test and in data not read from a blob. */
213
+ hint?: Hex;
214
+ /** Encoded operator: opCode(1) || each operand value as a 32-byte word, a context property as its ID. */
215
+ operator: Hex;
216
+ };
217
+
218
+ /** A loop: its body over each element of the array at `path`. */
219
+ export type LoopRule = {
220
+ scope: typeof Scope.LOOP;
221
+ quantifier: QuantifierCode;
222
+ /** BE16-encoded path from the arguments to the array. */
223
+ path: Hex;
224
+ /** The frame hint a blob carries for the path, absent in data not read from a blob. */
225
+ hint?: Hex;
226
+ body: BodyRule[];
227
+ };
228
+
229
+ /** A rule of a group: a test, a context test or a loop. */
230
+ export type Rule = BodyRule | LoopRule;
231
+
232
+ /** A policy: its selector, its descriptor and its groups of rules. */
193
233
  export type PolicyData = {
194
234
  isSelectorless: boolean;
195
235
  selector: Hex;
196
236
  descriptor: Hex;
197
- groups: Constraint[][];
198
- span?: Span;
237
+ groups: Rule[][];
199
238
  };
200
239
 
240
+ /**
241
+ * An operator as a policy stores it: its comparison, its polarity and its operands. Constants are loaded to 32-byte
242
+ * words of `operandType`. A context operand names its property.
243
+ */
244
+ export type DecodedOperator = {
245
+ /** Operator code with the `Op.NOT` bit intact. */
246
+ opCode: number;
247
+ /** Comparison bits of `opCode`. */
248
+ comparison: ComparisonCode;
249
+ /** True when `opCode` carries the `Op.NOT` bit. */
250
+ negated: boolean;
251
+ /**
252
+ * Type code of the operands: the target's type, the type a length compares as for a length comparison,
253
+ * or the context property's type.
254
+ */
255
+ operandType: number;
256
+ } & (
257
+ | {
258
+ source: typeof Source.CONSTANT;
259
+ /** Each constant loaded to its 32-byte word. */
260
+ values: Hex[];
261
+ }
262
+ | {
263
+ source: typeof Source.CONTEXT;
264
+ /** The context property the operand names. */
265
+ contextPropertyId: number;
266
+ }
267
+ );
268
+
201
269
  /** A collection of operators targeting a specific value. */
202
270
  export type Constraint = {
203
- scope: number;
271
+ scope: typeof Scope.CONTEXT | typeof Scope.CALLDATA;
204
272
  path: Hex;
205
273
  operators: Hex[];
206
- /** Compiled hint block as carried on the wire; absent when the constraint has no encoding. */
207
- hint?: Hex;
208
- span?: Span;
209
274
  };
210
275
 
211
276
  ///////////////////////////////////////////////////////////////////////////
212
- // Structural inspection types
277
+ // Inspection
213
278
  ///////////////////////////////////////////////////////////////////////////
214
279
 
215
- /** A decoded value with its byte position in the source blob. */
216
- export type Field<T> = { value: T; span: Span };
217
-
218
- /** Structural representation of a decoded parameter within the descriptor. */
219
- export type DecodedParam = {
220
- index: number;
280
+ /** A descriptor node: its type, its head size and the bytes it spans, with its fields or its element. */
281
+ export type DecodedNode = {
221
282
  typeCode: number;
222
283
  isDynamic: boolean;
284
+ /** Head size in bytes, zero for a dynamic type. */
223
285
  staticSize: number;
224
- path: Hex;
286
+ span: Span;
287
+ } & (
288
+ | { typeClass: "elementary" }
289
+ | { typeClass: "tuple"; fields: DecodedNode[] }
290
+ | { typeClass: "staticArray"; length: number; element: DecodedNode }
291
+ | { typeClass: "dynamicArray"; element: DecodedNode }
292
+ );
293
+
294
+ /** A descriptor: its header fields and one node per parameter. */
295
+ export type DecodedDescriptor = {
296
+ version: Field<number>;
297
+ paramCount: Field<number>;
298
+ params: DecodedNode[];
299
+ span: Span;
300
+ };
301
+
302
+ /** An array's meta word. */
303
+ export type ArrayMeta = {
304
+ /** The element type is dynamic. An offset word points to each element. */
305
+ isElementDynamic: boolean;
306
+ /** The array is dynamic. A length word is before the elements. */
307
+ isDynamicArray: boolean;
308
+ /** Element head size in words. */
309
+ strideWords: number;
310
+ };
311
+
312
+ /** A hop of a compiled hint: a plain hop enters the node behind an offset word, an element hop selects an element. */
313
+ export type DecodedHop = {
314
+ kind: "plain" | "element";
315
+ delta: Field<number>;
316
+ index: Field<number>;
317
+ meta: Field<ArrayMeta>;
225
318
  span: Span;
226
319
  };
227
320
 
228
- /** Structural representation of a decoded rule with per-field spans. */
229
- export type DecodedRule = {
321
+ /** A test hint: its hops, then the target's position, meta word and type code. */
322
+ export type DecodedHint = {
323
+ hopCount: Field<number>;
324
+ hops: DecodedHop[];
325
+ targetDelta: Field<number>;
326
+ /** The target's meta word. Every field is false or zero unless the target is a dynamic array. */
327
+ targetMeta: Field<ArrayMeta>;
328
+ typeCode: Field<number>;
329
+ span: Span;
330
+ };
331
+
332
+ /** A frame hint: its hops, then the array's position, declared length and meta word. */
333
+ export type DecodedFrameHint = {
334
+ hopCount: Field<number>;
335
+ hops: DecodedHop[];
336
+ arrayDelta: Field<number>;
337
+ /** Declared length of a static array, or zero for a dynamic array. */
338
+ count: Field<number>;
339
+ meta: Field<ArrayMeta>;
340
+ span: Span;
341
+ };
342
+
343
+ /** A test or a context test, each field with the bytes that contain it. */
344
+ export type DecodedTest = {
230
345
  ruleSize: Field<number>;
231
- scope: Field<number>;
346
+ scope: Field<typeof Scope.CONTEXT | typeof Scope.CALLDATA>;
232
347
  pathDepth: Field<number>;
348
+ /** The path steps, or a context test's property ID. */
233
349
  path: Field<Hex>;
234
- /** Compiled hint block; present for calldata rules only. */
235
- hint?: Field<Hex>;
350
+ /** The test hint, absent for a context test. */
351
+ hint?: DecodedHint;
352
+ /** Type code of the target: the hint's, or the context property's. */
353
+ targetType: number;
236
354
  opCode: Field<number>;
237
355
  dataLength: Field<number>;
356
+ /** The operands as stored. */
238
357
  data: Field<Hex>;
358
+ operator: DecodedOperator;
359
+ span: Span;
360
+ };
361
+
362
+ /** A loop rule, each field with the bytes that contain it. */
363
+ export type DecodedLoop = {
364
+ ruleSize: Field<number>;
365
+ scope: Field<typeof Scope.LOOP>;
366
+ quantifier: Field<QuantifierCode>;
367
+ pathDepth: Field<number>;
368
+ /** The array's path. */
369
+ path: Field<Hex>;
370
+ hint: DecodedFrameHint;
371
+ ruleCount: Field<number>;
372
+ body: DecodedTest[];
239
373
  span: Span;
240
374
  };
241
375
 
242
- /** Structural representation of a decoded group with metadata spans. */
376
+ /** A rule of a group: a test, a context test or a loop. */
377
+ export type DecodedRule = DecodedTest | DecodedLoop;
378
+
379
+ /** A group, each field with the bytes that contain it. */
243
380
  export type DecodedGroup = {
244
381
  ruleCount: Field<number>;
245
382
  groupSize: Field<number>;
@@ -247,101 +384,65 @@ export type DecodedGroup = {
247
384
  span: Span;
248
385
  };
249
386
 
250
- /** Structural representation of a decoded policy with full byte-level spans. */
387
+ /** A policy blob, each field with the bytes that contain it. */
251
388
  export type DecodedPolicy = {
252
- header: Field<number>;
389
+ header: Field<{ major: number; minor: number; isSelectorless: boolean }>;
390
+ /** The selector slot, zero for a selectorless policy. */
253
391
  selector: Field<Hex>;
254
392
  descLength: Field<number>;
255
- descriptor: { raw: Hex; header: Field<{ version: number; paramCount: number }>; params: DecodedParam[]; span: Span };
393
+ descriptor: DecodedDescriptor;
256
394
  groupCount: Field<number>;
257
395
  groups: DecodedGroup[];
258
396
  span: Span;
259
- version: number;
260
- isSelectorless: boolean;
261
397
  };
262
398
 
263
399
  ///////////////////////////////////////////////////////////////////////////
264
- // Validation issues
400
+ // Validation
265
401
  ///////////////////////////////////////////////////////////////////////////
266
402
 
267
403
  /** Severity of a validation issue. */
268
404
  export type IssueSeverity = "info" | "warning" | "error";
269
405
 
270
- /** Category of a validation issue. */
406
+ /** Kind of defect a validation issue reports. */
271
407
  export type IssueCategory = "typeMismatch" | "contradiction" | "redundancy" | "vacuity" | "compatibility";
272
408
 
273
- /**
274
- * Machine-readable code for a policy validation issue.
275
- *
276
- * Each code names one finding of the semantic validator, matching the same-named Solidity `IssueCode` constant.
277
- */
409
+ /** Machine-readable code of a validation issue. */
278
410
  export type IssueCode =
279
- // Type mismatch.
280
- | "VALUE_OP_ON_DYNAMIC"
281
- | "VALUE_OP_ON_COMPOSITE"
411
+ | "INVALID_DESCRIPTOR"
412
+ | "UNNAVIGABLE_PATH"
413
+ | "QUANTIFIER_ON_NON_ARRAY"
414
+ | "HINT_MISMATCH"
282
415
  | "NUMERIC_OP_ON_NON_NUMERIC"
283
416
  | "BITMASK_ON_INVALID"
284
417
  | "IN_ON_BOOL"
285
- | "LENGTH_ON_STATIC"
286
418
  | "CONTEXT_TYPE_MISMATCH"
287
- | "UNKNOWN_OPERATOR"
288
- | "NON_CANONICAL_OPERAND"
289
- | "UNNAVIGABLE_PATH"
290
- | "NESTED_QUANTIFIER"
291
- | "HINT_MISMATCH"
292
- // Contradiction.
293
- | "EQ_NEQ_CONTRADICTION"
294
- | "CONFLICTING_EQUALITY"
295
419
  | "OUT_OF_PHYSICAL_BOUNDS"
296
- | "IMPOSSIBLE_GT"
297
- | "IMPOSSIBLE_LT"
298
- | "BOUNDS_EXCLUDE_EQUALITY"
299
- | "IMPOSSIBLE_RANGE"
300
- | "SET_EXCLUDES_EQUALITY"
301
- | "EMPTY_SET_INTERSECTION"
302
- | "SET_FULLY_EXCLUDED"
303
- | "LENGTH_EQ_NEQ_CONTRADICTION"
304
- | "CONFLICTING_LENGTH"
305
- | "BOUNDS_EXCLUDE_LENGTH"
306
- | "IMPOSSIBLE_LENGTH_RANGE"
307
- | "OUT_OF_PHYSICAL_LENGTH_BOUNDS"
308
- | "IMPOSSIBLE_LENGTH_GT"
309
- | "IMPOSSIBLE_LENGTH_LT"
310
- | "BITMASK_CONTRADICTION"
311
- | "BITMASK_ANY_IMPOSSIBLE"
312
- | "UNSORTED_IN_SET"
313
- | "EMPTY_GROUP"
314
- // Redundancy.
420
+ | "CONTRADICTORY_RULE"
421
+ | "REDUNDANT_RULE"
315
422
  | "DOMINATED_BOUND"
316
- | "REDUNDANT_BOUND"
317
- | "SET_REDUCTION"
318
- | "SET_REDUNDANCY"
319
- | "SET_PARTIALLY_EXCLUDED"
320
423
  | "DOMINATED_LENGTH_BOUND"
321
- | "REDUNDANT_LENGTH_BOUND"
322
- | "REDUNDANT_BITMASK"
323
- | "DUPLICATE_CONSTRAINT"
324
- | "FUSIBLE_RANGE"
325
- | "FUSIBLE_LENGTH_RANGE"
326
- // Vacuity.
327
- | "VACUOUS_GTE"
328
- | "VACUOUS_LTE"
329
- | "VACUOUS_LENGTH_GTE"
330
- | "VACUOUS_LENGTH_LTE"
331
- | "VACUOUS_NEGATED_RANGE"
332
- | "VACUOUS_NEGATED_LENGTH_RANGE"
333
- // Compatibility.
334
- | "PATH_DEPTH_EXCEEDED"
424
+ | "VACUOUS_RULE"
335
425
  | "QUANTIFIER_OVER_STATIC_LIMIT"
336
- | "UNKNOWN_CONTEXT_PROPERTY"
337
- | "NEGATION_UNDER_ANY";
426
+ | "CONTEXT_ONLY_BODY";
427
+
428
+ /**
429
+ * The rule an issue names: its group, its position in the group and, for a test in a loop body, its position in the
430
+ * body. The location also carries the rule's scope, path and operator as decoded.
431
+ * A path in a body starts at the element.
432
+ */
433
+ export type RuleLocation = {
434
+ groupIndex: number;
435
+ ruleIndex: number;
436
+ bodyRuleIndex?: number;
437
+ scope: ScopeCode;
438
+ path: Hex;
439
+ operator?: Hex;
440
+ };
338
441
 
339
- /** A single validation issue found during policy analysis. */
340
- export type Issue = {
442
+ /** A validation issue: the rule it names, absent for one about the descriptor, and the values that show the defect. */
443
+ export type Issue = Partial<RuleLocation> & {
341
444
  severity: IssueSeverity;
342
445
  category: IssueCategory;
343
- groupIndex: number;
344
- constraintIndex: number;
345
446
  code: IssueCode;
346
447
  value1: Hex;
347
448
  value2: Hex;