@crediolabs/policy-synth 0.2.0 → 0.3.1

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 (249) hide show
  1. package/README.md +4 -4
  2. package/dist/adapters/interpreter/adapter.d.ts +19 -25
  3. package/dist/adapters/interpreter/adapter.js +54 -487
  4. package/dist/errors.d.ts +1 -1
  5. package/dist/index.d.ts +1 -4
  6. package/dist/index.js +1 -4
  7. package/dist/install/build-add-context-rule.d.ts +3 -19
  8. package/dist/install/build-add-context-rule.js +3 -98
  9. package/dist/install/build-install-policy.d.ts +4 -53
  10. package/dist/install/build-install-policy.js +44 -123
  11. package/dist/install/index.d.ts +0 -2
  12. package/dist/install/index.js +0 -7
  13. package/dist/predicate/decode.d.ts +1 -1
  14. package/dist/predicate/decode.js +2 -70
  15. package/dist/predicate/encode.js +45 -229
  16. package/dist/predicate/from-json.js +1 -42
  17. package/dist/record/decode.js +3 -8
  18. package/dist/record/freshness.d.ts +17 -11
  19. package/dist/record/freshness.js +28 -18
  20. package/dist/review-card/builder.d.ts +4 -3
  21. package/dist/review-card/builder.js +18 -162
  22. package/dist/review-card/cross-check.js +2 -105
  23. package/dist/review-card/render-leaf.d.ts +4 -0
  24. package/dist/review-card/render-leaf.js +47 -0
  25. package/dist/run/index.d.ts +36 -139
  26. package/dist/run/index.js +125 -437
  27. package/dist/run/schemas.d.ts +168 -1929
  28. package/dist/run/schemas.js +40 -269
  29. package/dist/{synth → simulate}/deny-cases.d.ts +4 -8
  30. package/dist/simulate/deny-cases.js +282 -0
  31. package/dist/simulate/evaluate.d.ts +18 -0
  32. package/dist/simulate/evaluate.js +271 -0
  33. package/dist/simulate/index.d.ts +4 -0
  34. package/dist/simulate/index.js +8 -0
  35. package/dist/synth/compose-from-recording.d.ts +24 -51
  36. package/dist/synth/compose-from-recording.js +120 -282
  37. package/dist/synth/index.d.ts +0 -5
  38. package/dist/synth/index.js +0 -5
  39. package/dist/synth/synthesize-from-recording.d.ts +1 -29
  40. package/dist/synth/synthesize-from-recording.js +41 -356
  41. package/dist/types.d.ts +8 -60
  42. package/dist/types.js +5 -2
  43. package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
  44. package/dist-cjs/adapters/interpreter/adapter.js +56 -489
  45. package/dist-cjs/errors.d.ts +1 -1
  46. package/dist-cjs/index.d.ts +1 -4
  47. package/dist-cjs/index.js +1 -4
  48. package/dist-cjs/install/build-add-context-rule.d.ts +3 -19
  49. package/dist-cjs/install/build-add-context-rule.js +1 -97
  50. package/dist-cjs/install/build-install-policy.d.ts +4 -53
  51. package/dist-cjs/install/build-install-policy.js +44 -122
  52. package/dist-cjs/install/index.d.ts +0 -2
  53. package/dist-cjs/install/index.js +2 -23
  54. package/dist-cjs/predicate/decode.d.ts +1 -1
  55. package/dist-cjs/predicate/decode.js +2 -70
  56. package/dist-cjs/predicate/encode.js +45 -229
  57. package/dist-cjs/predicate/from-json.js +1 -42
  58. package/dist-cjs/record/decode.js +3 -8
  59. package/dist-cjs/record/freshness.d.ts +17 -11
  60. package/dist-cjs/record/freshness.js +28 -18
  61. package/dist-cjs/review-card/builder.d.ts +4 -3
  62. package/dist-cjs/review-card/builder.js +23 -167
  63. package/dist-cjs/review-card/cross-check.js +7 -110
  64. package/dist-cjs/review-card/render-leaf.d.ts +4 -0
  65. package/dist-cjs/review-card/render-leaf.js +52 -0
  66. package/dist-cjs/run/index.d.ts +36 -139
  67. package/dist-cjs/run/index.js +125 -444
  68. package/dist-cjs/run/schemas.d.ts +168 -1929
  69. package/dist-cjs/run/schemas.js +41 -270
  70. package/dist-cjs/{synth → simulate}/deny-cases.d.ts +4 -8
  71. package/dist-cjs/simulate/deny-cases.js +286 -0
  72. package/dist-cjs/simulate/evaluate.d.ts +18 -0
  73. package/dist-cjs/simulate/evaluate.js +274 -0
  74. package/dist-cjs/simulate/index.d.ts +4 -0
  75. package/dist-cjs/simulate/index.js +13 -0
  76. package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
  77. package/dist-cjs/synth/compose-from-recording.js +120 -282
  78. package/dist-cjs/synth/index.d.ts +0 -5
  79. package/dist-cjs/synth/index.js +1 -11
  80. package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
  81. package/dist-cjs/synth/synthesize-from-recording.js +39 -354
  82. package/dist-cjs/types.d.ts +8 -60
  83. package/dist-cjs/types.js +6 -3
  84. package/package.json +5 -5
  85. package/src/adapters/interpreter/adapter.ts +76 -572
  86. package/src/errors.ts +0 -19
  87. package/src/index.ts +1 -4
  88. package/src/install/build-add-context-rule.ts +5 -139
  89. package/src/install/build-install-policy.ts +93 -214
  90. package/src/install/index.ts +0 -34
  91. package/src/predicate/decode.ts +2 -70
  92. package/src/predicate/encode.ts +49 -261
  93. package/src/predicate/from-json.ts +1 -43
  94. package/src/record/decode.ts +3 -8
  95. package/src/record/freshness.ts +29 -18
  96. package/src/review-card/builder.ts +19 -168
  97. package/src/review-card/cross-check.ts +3 -105
  98. package/src/review-card/render-leaf.ts +48 -0
  99. package/src/run/index.ts +144 -572
  100. package/src/run/schemas.ts +42 -291
  101. package/src/simulate/deny-cases.ts +334 -0
  102. package/src/simulate/evaluate.ts +284 -0
  103. package/src/simulate/index.ts +11 -0
  104. package/src/synth/compose-from-recording.ts +148 -347
  105. package/src/synth/index.ts +0 -13
  106. package/src/synth/synthesize-from-recording.ts +43 -456
  107. package/src/types.ts +13 -49
  108. package/dist/adapters/interpreter/index.d.ts +0 -1
  109. package/dist/adapters/interpreter/index.js +0 -2
  110. package/dist/adapters/oz/adapter.d.ts +0 -20
  111. package/dist/adapters/oz/adapter.js +0 -295
  112. package/dist/adapters/oz/index.d.ts +0 -1
  113. package/dist/adapters/oz/index.js +0 -2
  114. package/dist/codegen/compile-gate.d.ts +0 -33
  115. package/dist/codegen/compile-gate.js +0 -124
  116. package/dist/codegen/index.d.ts +0 -2
  117. package/dist/codegen/index.js +0 -8
  118. package/dist/codegen/template.d.ts +0 -18
  119. package/dist/codegen/template.js +0 -148
  120. package/dist/install/authority-overlap.d.ts +0 -134
  121. package/dist/install/authority-overlap.js +0 -0
  122. package/dist/install/build-install-predicate.d.ts +0 -96
  123. package/dist/install/build-install-predicate.js +0 -444
  124. package/dist/install/build-merge-policy.d.ts +0 -70
  125. package/dist/install/build-merge-policy.js +0 -130
  126. package/dist/install/plan-merge-policy.d.ts +0 -49
  127. package/dist/install/plan-merge-policy.js +0 -86
  128. package/dist/install/read-account-rules.d.ts +0 -100
  129. package/dist/install/read-account-rules.js +0 -283
  130. package/dist/ir/index.d.ts +0 -1
  131. package/dist/ir/index.js +0 -2
  132. package/dist/ir/types.d.ts +0 -140
  133. package/dist/ir/types.js +0 -11
  134. package/dist/mandate/index.d.ts +0 -2
  135. package/dist/mandate/index.js +0 -2
  136. package/dist/mandate/to-ir.d.ts +0 -3
  137. package/dist/mandate/to-ir.js +0 -60
  138. package/dist/mandate/types.d.ts +0 -20
  139. package/dist/mandate/types.js +0 -8
  140. package/dist/seams/index.d.ts +0 -1
  141. package/dist/seams/index.js +0 -2
  142. package/dist/seams/types.d.ts +0 -66
  143. package/dist/seams/types.js +0 -11
  144. package/dist/synth/deny-cases.js +0 -562
  145. package/dist/synth/evaluate.d.ts +0 -39
  146. package/dist/synth/evaluate.js +0 -551
  147. package/dist/synth/harness.d.ts +0 -28
  148. package/dist/synth/harness.js +0 -47
  149. package/dist/synth/minimize.d.ts +0 -4
  150. package/dist/synth/minimize.js +0 -38
  151. package/dist/synth/permit-context.d.ts +0 -15
  152. package/dist/synth/permit-context.js +0 -116
  153. package/dist/synth/predicate-literals.d.ts +0 -5
  154. package/dist/synth/predicate-literals.js +0 -25
  155. package/dist/synth/synthesize-from-mandate.d.ts +0 -20
  156. package/dist/synth/synthesize-from-mandate.js +0 -59
  157. package/dist/verify/envelope.d.ts +0 -15
  158. package/dist/verify/envelope.js +0 -22
  159. package/dist/verify/index.d.ts +0 -3
  160. package/dist/verify/index.js +0 -3
  161. package/dist/verify/simulate.d.ts +0 -31
  162. package/dist/verify/simulate.js +0 -258
  163. package/dist/verify/verify.d.ts +0 -21
  164. package/dist/verify/verify.js +0 -189
  165. package/dist-cjs/adapters/interpreter/index.d.ts +0 -1
  166. package/dist-cjs/adapters/interpreter/index.js +0 -8
  167. package/dist-cjs/adapters/oz/adapter.d.ts +0 -20
  168. package/dist-cjs/adapters/oz/adapter.js +0 -300
  169. package/dist-cjs/adapters/oz/index.d.ts +0 -1
  170. package/dist-cjs/adapters/oz/index.js +0 -8
  171. package/dist-cjs/codegen/compile-gate.d.ts +0 -33
  172. package/dist-cjs/codegen/compile-gate.js +0 -128
  173. package/dist-cjs/codegen/index.d.ts +0 -2
  174. package/dist-cjs/codegen/index.js +0 -14
  175. package/dist-cjs/codegen/template.d.ts +0 -18
  176. package/dist-cjs/codegen/template.js +0 -151
  177. package/dist-cjs/install/authority-overlap.d.ts +0 -134
  178. package/dist-cjs/install/authority-overlap.js +0 -0
  179. package/dist-cjs/install/build-install-predicate.d.ts +0 -96
  180. package/dist-cjs/install/build-install-predicate.js +0 -479
  181. package/dist-cjs/install/build-merge-policy.d.ts +0 -70
  182. package/dist-cjs/install/build-merge-policy.js +0 -134
  183. package/dist-cjs/install/plan-merge-policy.d.ts +0 -49
  184. package/dist-cjs/install/plan-merge-policy.js +0 -90
  185. package/dist-cjs/install/read-account-rules.d.ts +0 -100
  186. package/dist-cjs/install/read-account-rules.js +0 -296
  187. package/dist-cjs/ir/index.d.ts +0 -1
  188. package/dist-cjs/ir/index.js +0 -3
  189. package/dist-cjs/ir/types.d.ts +0 -140
  190. package/dist-cjs/ir/types.js +0 -12
  191. package/dist-cjs/mandate/index.d.ts +0 -2
  192. package/dist-cjs/mandate/index.js +0 -6
  193. package/dist-cjs/mandate/to-ir.d.ts +0 -3
  194. package/dist-cjs/mandate/to-ir.js +0 -63
  195. package/dist-cjs/mandate/types.d.ts +0 -20
  196. package/dist-cjs/mandate/types.js +0 -9
  197. package/dist-cjs/seams/index.d.ts +0 -1
  198. package/dist-cjs/seams/index.js +0 -3
  199. package/dist-cjs/seams/types.d.ts +0 -66
  200. package/dist-cjs/seams/types.js +0 -12
  201. package/dist-cjs/synth/deny-cases.js +0 -568
  202. package/dist-cjs/synth/evaluate.d.ts +0 -39
  203. package/dist-cjs/synth/evaluate.js +0 -554
  204. package/dist-cjs/synth/harness.d.ts +0 -28
  205. package/dist-cjs/synth/harness.js +0 -50
  206. package/dist-cjs/synth/minimize.d.ts +0 -4
  207. package/dist-cjs/synth/minimize.js +0 -41
  208. package/dist-cjs/synth/permit-context.d.ts +0 -15
  209. package/dist-cjs/synth/permit-context.js +0 -119
  210. package/dist-cjs/synth/predicate-literals.d.ts +0 -5
  211. package/dist-cjs/synth/predicate-literals.js +0 -28
  212. package/dist-cjs/synth/synthesize-from-mandate.d.ts +0 -20
  213. package/dist-cjs/synth/synthesize-from-mandate.js +0 -62
  214. package/dist-cjs/verify/envelope.d.ts +0 -15
  215. package/dist-cjs/verify/envelope.js +0 -23
  216. package/dist-cjs/verify/index.d.ts +0 -3
  217. package/dist-cjs/verify/index.js +0 -8
  218. package/dist-cjs/verify/simulate.d.ts +0 -31
  219. package/dist-cjs/verify/simulate.js +0 -261
  220. package/dist-cjs/verify/verify.d.ts +0 -21
  221. package/dist-cjs/verify/verify.js +0 -192
  222. package/src/adapters/interpreter/index.ts +0 -8
  223. package/src/adapters/oz/adapter.ts +0 -376
  224. package/src/adapters/oz/index.ts +0 -9
  225. package/src/codegen/compile-gate.ts +0 -167
  226. package/src/codegen/index.ts +0 -17
  227. package/src/codegen/template.ts +0 -165
  228. package/src/install/authority-overlap.ts +0 -0
  229. package/src/install/build-merge-policy.ts +0 -219
  230. package/src/install/plan-merge-policy.ts +0 -133
  231. package/src/install/read-account-rules.ts +0 -376
  232. package/src/ir/index.ts +0 -13
  233. package/src/ir/types.ts +0 -132
  234. package/src/mandate/index.ts +0 -4
  235. package/src/mandate/to-ir.ts +0 -71
  236. package/src/mandate/types.ts +0 -21
  237. package/src/seams/index.ts +0 -11
  238. package/src/seams/types.ts +0 -81
  239. package/src/synth/deny-cases.ts +0 -663
  240. package/src/synth/evaluate.ts +0 -613
  241. package/src/synth/harness.ts +0 -68
  242. package/src/synth/minimize.ts +0 -48
  243. package/src/synth/permit-context.ts +0 -136
  244. package/src/synth/predicate-literals.ts +0 -27
  245. package/src/synth/synthesize-from-mandate.ts +0 -82
  246. package/src/verify/envelope.ts +0 -28
  247. package/src/verify/index.ts +0 -5
  248. package/src/verify/simulate.ts +0 -311
  249. package/src/verify/verify.ts +0 -243
@@ -55,21 +55,6 @@ function expectSymbol(v, what) {
55
55
  throw malformed(`${what} is not a symbol`);
56
56
  return v.sym().toString();
57
57
  }
58
- function expectAddress(v, what) {
59
- if (!v || v.switch() !== xdr.ScValType.scvAddress())
60
- throw malformed(`${what} is not an address`);
61
- return Address.fromScAddress(v.address()).toString();
62
- }
63
- function expectU64(v, what) {
64
- if (!v || v.switch() !== xdr.ScValType.scvU64())
65
- throw malformed(`${what} is not a u64`);
66
- return v.u64().toString();
67
- }
68
- function expectI128(v, what) {
69
- if (!v || v.switch() !== xdr.ScValType.scvI128())
70
- throw malformed(`${what} is not an i128`);
71
- return scValToBigInt(v).toString();
72
- }
73
58
  /** Arity check with the same intent as the Rust `check_arity`: a selector with
74
59
  * the wrong element count is malformed, not silently truncated. */
75
60
  function arity(items, n, selector) {
@@ -99,48 +84,6 @@ function decodeSelectorLeaf(items, sym) {
99
84
  element: expectU32(items[2], 'call_arg_field element'),
100
85
  field: expectSymbol(items[3], 'call_arg_field field'),
101
86
  };
102
- case 'call_arg_scaled':
103
- arity(items, 4, sym);
104
- return {
105
- kind: 'call_arg_scaled',
106
- index: expectU32(items[1], 'call_arg_scaled index'),
107
- num: expectI128(items[2], 'call_arg_scaled num'),
108
- den: expectI128(items[3], 'call_arg_scaled den'),
109
- };
110
- case 'amount':
111
- arity(items, 2, sym);
112
- return { kind: 'amount', token: expectAddress(items[1], 'amount token') };
113
- case 'window_spent':
114
- arity(items, 3, sym);
115
- return {
116
- kind: 'window_spent',
117
- token: expectAddress(items[1], 'window_spent token'),
118
- windowSeconds: Number(expectU64(items[2], 'window_spent windowSeconds')),
119
- };
120
- case 'now':
121
- arity(items, 1, sym);
122
- return { kind: 'now' };
123
- case 'valid_until':
124
- // The encoder refuses to emit this and the interpreter refuses it at
125
- // install, so it cannot appear in a document that installed. Decoding it
126
- // anyway would present a rule the chain would never have accepted.
127
- throw malformed('valid_until is not a usable predicate leaf');
128
- case 'invocation_count':
129
- arity(items, 2, sym);
130
- return {
131
- kind: 'invocation_count_in_window',
132
- windowSecs: Number(expectU64(items[1], 'invocation_count windowSecs')),
133
- };
134
- case 'oracle_price':
135
- arity(items, 2, sym);
136
- return { kind: 'oracle_price', asset: expectAddress(items[1], 'oracle_price asset') };
137
- case 'oracle_threshold':
138
- arity(items, 3, sym);
139
- return {
140
- kind: 'oracle_threshold',
141
- value: expectI128(items[1], 'oracle_threshold value'),
142
- decimals: expectU32(items[2], 'oracle_threshold decimals'),
143
- };
144
87
  default:
145
88
  // Deliberately NOT a literal_vec fallback - see the header note.
146
89
  throw malformed(`unknown selector symbol '${sym}'`);
@@ -164,39 +107,28 @@ export function decodeLeaf(v) {
164
107
  return { kind: 'literal_symbol', value: v.sym().toString() };
165
108
  case xdr.ScValType.scvU32():
166
109
  return { kind: 'literal_u32', value: v.u32() };
167
- case xdr.ScValType.scvU64():
168
- return { kind: 'literal_u64', value: v.u64().toString() };
169
110
  case xdr.ScValType.scvI128():
170
111
  return { kind: 'literal_i128', value: scValToBigInt(v).toString() };
171
- case xdr.ScValType.scvBytes():
172
- return { kind: 'literal_bytes', value: Buffer.from(v.bytes()).toString('hex') };
173
112
  default:
174
113
  throw malformed(`unsupported leaf value type ${v.switch().name}`);
175
114
  }
176
115
  }
177
- /** One node. `not` wraps a NODE; the comparison ops wrap two LEAVES. */
116
+ /** One node. The comparison ops wrap two LEAVES. */
178
117
  export function decodeNode(v) {
179
118
  const items = expectVec(v, 'node');
180
119
  const op = selectorSymbol(items);
181
120
  if (op === null)
182
121
  throw malformed('node does not start with an operator symbol');
183
122
  switch (op) {
184
- case 'and':
185
- case 'or': {
123
+ case 'and': {
186
124
  arity(items, 2, op);
187
125
  const children = expectVec(items[1], `${op} children`).map(decodeNode);
188
126
  if (children.length === 0)
189
127
  throw malformed(`${op} has no children`);
190
128
  return { op, children };
191
129
  }
192
- case 'not':
193
- arity(items, 2, op);
194
- return { op: 'not', child: decodeNode(items[1]) };
195
130
  case 'eq':
196
- case 'lt':
197
131
  case 'lte':
198
- case 'gt':
199
- case 'gte':
200
132
  arity(items, 3, op);
201
133
  return {
202
134
  op,
@@ -2,7 +2,7 @@
2
2
  //
3
3
  // Pure function. Maps a `PredicateNode` AST to the canonical ScVal wire format:
4
4
  // - every node is a `ScVal::Vec` whose head element is the tag `ScVal::Symbol`
5
- // - children of `and` / `or` are sorted ascending by their canonical XDR bytes
5
+ // - children of `and` are sorted ascending by their canonical XDR bytes
6
6
  // - `in` haystacks are ALWAYS sorted by canonical XDR bytes (pure set
7
7
  // membership); an EXACT ordered sequence is expressed as
8
8
  // `eq(selector, literal_vec)` where the `literal_vec` element order is
@@ -17,12 +17,6 @@
17
17
  // Caps from `PREDICATE_CAPS` are enforced BEFORE returning; a cap breach throws
18
18
  // a `ToolError` with the matching error code and `severity: 'error'`.
19
19
  //
20
- // One gap remains, deliberately: the `amount` / `window_spent` leaf branches
21
- // below are dead ABI - the contract's grammar no longer has those selector
22
- // symbols, so a predicate carrying one is MALFORMED at decode. The interpreter
23
- // adapter reports them as uncovered before lowering, so the product path
24
- // cannot emit one; removing the branches (and the leaf kinds) is a separate
25
- // change.
26
20
  import { createHash } from 'node:crypto';
27
21
  import { Address, xdr } from '@stellar/stellar-sdk';
28
22
  import { PREDICATE_CAPS } from "../types.js";
@@ -45,27 +39,22 @@ export function encodePredicate(node) {
45
39
  throw capError('IN_OPERAND_LIMIT', `\`in\` operand count ${c.count} exceeds MAX_IN_OPERAND_COUNT ${PREDICATE_CAPS.MAX_IN_OPERAND_COUNT}`);
46
40
  }
47
41
  }
48
- // Two-round confirmation costs 2 oracle reads per UNIQUE asset referenced.
49
- const oracleReads = stats.oracleAssets.size * 2;
50
- if (oracleReads > PREDICATE_CAPS.MAX_ORACLE_READS) {
51
- throw capError('PREDICATE_ORACLE_OVER_LIMIT', `oracle reads ${oracleReads} exceed MAX_ORACLE_READS ${PREDICATE_CAPS.MAX_ORACLE_READS}`);
42
+ // Minimum constraint. A predicate with no selector leaf compares literals
43
+ // to literals, so it is trivially true or false and would permit everything
44
+ // or nothing forever. The contract refuses it at install with
45
+ // SELECTOR_LEAF_REQUIRED (216); refuse it here too so the failure is caught
46
+ // before a transaction is built.
47
+ if (stats.selectorLeaves === 0) {
48
+ throw capError('MALFORMED_PREDICATE', 'predicate constrains nothing: every comparison has literals on both sides. The contract refuses it at install (SELECTOR_LEAF_REQUIRED). Pin the call itself - contract, method or argument.');
52
49
  }
53
- if (stats.oracleAssets.size > 0 && stats.nonOracleSelectorLeaves === 0) {
54
- throw capError('MALFORMED_PREDICATE', 'predicate constrains nothing but an oracle price: the contract refuses it at install (dsl.rs MissingNonOracleEnvelope). Pin the call itself (contract / method / argument) alongside the price bound.');
55
- }
56
- // --- pass 1.5: leaf-value validation (Rust `validate_scaled_ratios` + the
57
- // broader cap-set gate the contract enforces at install). Defense in depth:
58
- // the TS self-verify pipeline should reject the same shapes Rust install
59
- // refuses, so a hand-crafted predicate that simulate/verify green-lights
60
- // cannot later be refused at the on-chain install step. The checks:
61
- // - u32 fields in range (call_arg index, literal_u32 value, oracle
62
- // threshold decimals, etc.) - the contract decodes as u32
63
- // - i128 positivity where required (literal_i128 for amount/window
64
- // caps; `den`/`num` for scaled ratios) - a negative cap would
65
- // permit everything
66
- // - hex even-length (literal_bytes) - `Buffer.from(v, 'hex')` silently
67
- // drops non-hex chars, so 'zz' becomes empty bytes
68
- // - scaled-ratio num>0 && den>0 - mirrors dsl.rs:661-704
50
+ // --- pass 1.5: leaf-value validation against the cap-set gate the contract
51
+ // enforces at install. Defense in depth: the TS self-verify pipeline should
52
+ // reject the same shapes Rust install refuses, so a hand-crafted predicate
53
+ // that simulate/verify green-lights cannot later be refused at the on-chain
54
+ // install step. The checks are the u32 ranges on the argument selectors
55
+ // (`call_arg.index`, `call_arg_len.index`, `call_arg_field.index` and
56
+ // `.element`) - the contract decodes each as u32, so an out-of-range value
57
+ // would fail there instead.
69
58
  // Throws `MALFORMED_PREDICATE` so the error stays a ToolError shape.
70
59
  validateLeafValues(node);
71
60
  // --- pass 2: build + canonicalise the ScVal ---
@@ -80,48 +69,35 @@ export function encodePredicate(node) {
80
69
  }
81
70
  function computeStats(node) {
82
71
  const inCounts = [];
83
- const oracleAssets = new Set();
84
- const counters = { nonOracleSelectorLeaves: 0 };
85
- const { depth, leaves } = walk(node, inCounts, oracleAssets, counters, false);
72
+ const counters = { selectorLeaves: 0 };
73
+ const { depth, leaves } = walk(node, inCounts, counters);
86
74
  return {
87
75
  depth,
88
76
  leaves,
89
77
  inCounts,
90
- oracleAssets,
91
- nonOracleSelectorLeaves: counters.nonOracleSelectorLeaves,
78
+ selectorLeaves: counters.selectorLeaves,
92
79
  };
93
80
  }
94
- function walk(node, inCounts, oracleAssets, counters,
95
- /** True once the walk is under a `not` or an `or`, where an oracle read
96
- * becomes a condition the caller can satisfy by taking the other branch. */
97
- negatedOrDisjunctive) {
81
+ function walk(node, inCounts, counters) {
98
82
  switch (node.op) {
99
- case 'and':
100
- case 'or': {
83
+ case 'and': {
101
84
  if (node.children.length === 0) {
102
85
  throw capError('MALFORMED_PREDICATE', `\`${node.op}\` with no children: the contract refuses it at decode (MALFORMED_PREDICATE), so it can never be installed`);
103
86
  }
104
87
  let maxChildDepth = 0;
105
88
  let totalLeaves = 0;
106
89
  for (const c of node.children) {
107
- const child = walk(c, inCounts, oracleAssets, counters, negatedOrDisjunctive || node.op === 'or');
90
+ const child = walk(c, inCounts, counters);
108
91
  if (child.depth > maxChildDepth)
109
92
  maxChildDepth = child.depth;
110
93
  totalLeaves += child.leaves;
111
94
  }
112
95
  return { depth: maxChildDepth + 1, leaves: totalLeaves };
113
96
  }
114
- case 'not': {
115
- const child = walk(node.child, inCounts, oracleAssets, counters, true);
116
- return { depth: child.depth + 1, leaves: child.leaves };
117
- }
118
97
  case 'eq':
119
- case 'lt':
120
- case 'lte':
121
- case 'gt':
122
- case 'gte': {
123
- collectOracle(node.left, oracleAssets, counters, negatedOrDisjunctive);
124
- collectOracle(node.right, oracleAssets, counters, negatedOrDisjunctive);
98
+ case 'lte': {
99
+ collectSelector(node.left, counters);
100
+ collectSelector(node.right, counters);
125
101
  return { depth: 1, leaves: leafCount(node.left) + leafCount(node.right) };
126
102
  }
127
103
  case 'in': {
@@ -129,10 +105,10 @@ negatedOrDisjunctive) {
129
105
  throw capError('MALFORMED_PREDICATE', '`in` with an empty haystack: the contract refuses it at decode (MALFORMED_PREDICATE), so it can never be installed');
130
106
  }
131
107
  inCounts.push({ count: node.haystack.length });
132
- collectOracle(node.needle, oracleAssets, counters, negatedOrDisjunctive);
108
+ collectSelector(node.needle, counters);
133
109
  let haystackLeaves = 0;
134
110
  for (const h of node.haystack) {
135
- collectOracle(h, oracleAssets, counters, negatedOrDisjunctive);
111
+ collectSelector(h, counters);
136
112
  haystackLeaves += leafCount(h);
137
113
  }
138
114
  return {
@@ -154,45 +130,27 @@ function leafCount(leaf) {
154
130
  }
155
131
  return 1;
156
132
  }
157
- function collectOracle(leaf, oracleAssets, counters, negatedOrDisjunctive) {
158
- // literal_vec is the only nested leaf; recurse so an oracle_price buried in a
159
- // vector literal (which the lowering would forbid, but the cap-walker must
160
- // still see) is counted toward the oracle-read budget.
161
- if (leaf.kind === 'oracle_price') {
162
- if (negatedOrDisjunctive) {
163
- throw capError('ORACLE_LEAF_INVALID_POSITION', `oracle_price(${leaf.asset}) sits under a \`not\`/\`or\`: the contract refuses that position at install (dsl.rs validate_oracle_placement). An oracle bound must be a conjunct the call has to satisfy.`);
164
- }
165
- oracleAssets.add(leaf.asset);
166
- }
167
- else if (leaf.kind === 'literal_vec') {
133
+ function collectSelector(leaf, counters) {
134
+ // literal_vec is the only nested leaf; recurse so a selector buried in a
135
+ // vector literal is still counted. Mirrors `dsl::has_selector_leaf`.
136
+ if (leaf.kind === 'literal_vec') {
168
137
  for (const el of leaf.elements)
169
- collectOracle(el, oracleAssets, counters, negatedOrDisjunctive);
138
+ collectSelector(el, counters);
170
139
  }
171
- else if (!leaf.kind.startsWith('literal_') && leaf.kind !== 'oracle_threshold') {
172
- // A threshold is the oracle compare's operand, not an independent
173
- // constraint. Counting it would let an oracle-only predicate satisfy the
174
- // non-oracle envelope with its own bound (mirrors collect_oracle_leaf in
175
- // dsl.rs, where it sits with the literals).
176
- counters.nonOracleSelectorLeaves += 1;
140
+ else if (!leaf.kind.startsWith('literal_')) {
141
+ counters.selectorLeaves += 1;
177
142
  }
178
143
  }
179
144
  function encodeNode(node) {
180
145
  switch (node.op) {
181
- case 'and':
182
- case 'or': {
146
+ case 'and': {
183
147
  const encoded = node.children.map(encodeNode);
184
148
  // sort children by their canonical XDR bytes ascending.
185
149
  const sorted = sortByCanonicalBytes(encoded);
186
150
  return xdr.ScVal.scvVec([symbol(node.op), xdr.ScVal.scvVec(sorted)]);
187
151
  }
188
- case 'not': {
189
- return xdr.ScVal.scvVec([symbol('not'), encodeNode(node.child)]);
190
- }
191
152
  case 'eq':
192
- case 'lt':
193
- case 'lte':
194
- case 'gt':
195
- case 'gte': {
153
+ case 'lte': {
196
154
  return xdr.ScVal.scvVec([symbol(node.op), encodeLeaf(node.left), encodeLeaf(node.right)]);
197
155
  }
198
156
  case 'in': {
@@ -223,47 +181,6 @@ function encodeLeaf(leaf) {
223
181
  xdr.ScVal.scvU32(leaf.element),
224
182
  xdr.ScVal.scvSymbol(leaf.field),
225
183
  ]);
226
- case 'call_arg_scaled':
227
- // Wire shape `vec[symbol, u32, i128, i128]` mirrors the Rust decoder's
228
- // expectation in dsl.rs SEL_CALL_ARG_SCALED. `scvI128FromDecimal` is
229
- // the same range-checked helper used by `literal_i128`; an out-of-range
230
- // num/den is refused at encode time (rather than at install on chain).
231
- return xdr.ScVal.scvVec([
232
- symbol('call_arg_scaled'),
233
- xdr.ScVal.scvU32(leaf.index),
234
- scvI128FromDecimal(leaf.num),
235
- scvI128FromDecimal(leaf.den),
236
- ]);
237
- case 'amount':
238
- return xdr.ScVal.scvVec([symbol('amount'), scvAddressFromStrkey(leaf.token)]);
239
- case 'window_spent':
240
- return xdr.ScVal.scvVec([
241
- symbol('window_spent'),
242
- scvAddressFromStrkey(leaf.token),
243
- scvU64FromValue(leaf.windowSeconds),
244
- ]);
245
- case 'now':
246
- return xdr.ScVal.scvVec([symbol('now')]);
247
- case 'valid_until':
248
- // The interpreter refuses this leaf at install: it never sources
249
- // valid_until_ledger, so a policy built on it would deny forever.
250
- // Expiry belongs to the smart account, through the context rule's
251
- // validUntilLedger. Refuse at synthesis so the author finds out here
252
- // rather than from an install that always fails.
253
- throw new Error('valid_until is not a usable predicate leaf: the interpreter never sources it and ' +
254
- 'refuses it at install. Put expiry on the context rule (validUntilLedger) instead.');
255
- case 'invocation_count_in_window':
256
- return xdr.ScVal.scvVec([symbol('invocation_count'), scvU64FromValue(leaf.windowSecs)]);
257
- case 'oracle_price':
258
- return xdr.ScVal.scvVec([symbol('oracle_price'), scvAddressFromStrkey(leaf.asset)]);
259
- case 'oracle_threshold':
260
- // Arity 3, matching SEL_ORACLE_THRESHOLD in dsl.rs. The declared basis
261
- // travels with the value so the contract never has to assume one.
262
- return xdr.ScVal.scvVec([
263
- symbol('oracle_threshold'),
264
- scvI128FromDecimal(leaf.value),
265
- xdr.ScVal.scvU32(leaf.decimals),
266
- ]);
267
184
  case 'literal_address':
268
185
  return scvAddressFromStrkey(leaf.value);
269
186
  case 'literal_i128':
@@ -272,10 +189,6 @@ function encodeLeaf(leaf) {
272
189
  return xdr.ScVal.scvSymbol(leaf.value);
273
190
  case 'literal_u32':
274
191
  return xdr.ScVal.scvU32(leaf.value);
275
- case 'literal_u64':
276
- return scvU64FromValue(leaf.value);
277
- case 'literal_bytes':
278
- return xdr.ScVal.scvBytes(Buffer.from(leaf.value, 'hex'));
279
192
  case 'literal_vec':
280
193
  // Bare ScVal::Vec of element encodings - order is preserved verbatim
281
194
  // because the order IS the semantic (exact ordered sequence equality).
@@ -293,11 +206,6 @@ function sortByCanonicalBytes(values) {
293
206
  function scvAddressFromStrkey(strkey) {
294
207
  return xdr.ScVal.scvAddress(Address.fromString(strkey).toScAddress());
295
208
  }
296
- function scvU64FromValue(value) {
297
- // The Uint64 (UnsignedHyper) constructor accepts string | bigint | number;
298
- // string is safest for values > 2^53.
299
- return xdr.ScVal.scvU64(new xdr.Uint64(String(value)));
300
- }
301
209
  /** Build `ScVal::I128(Int128Parts{hi, lo})` from a signed decimal string.
302
210
  * `Int128Parts` encodes the value as `(hi << 64) + lo` with `hi` a SIGNED
303
211
  * 64-bit int and `lo` an UNSIGNED 64-bit int (this is NOT signed-magnitude).
@@ -313,9 +221,8 @@ function scvI128FromDecimal(decimal) {
313
221
  if (hi < INT64_MIN || hi > INT64_MAX) {
314
222
  throw capError('MALFORMED_PREDICATE', `literal_i128 value ${decimal} is outside the Int128 range`);
315
223
  }
316
- if (lo < 0n || lo > UINT64_MAX) {
317
- throw capError('MALFORMED_PREDICATE', `literal_i128 value ${decimal} has an invalid Int128Parts.lo`);
318
- }
224
+ // `lo` needs no range check: masking with UINT64_MAX puts it in
225
+ // [0, 2^64-1] for every v, negative included.
319
226
  return xdr.ScVal.scvI128(new xdr.Int128Parts({
320
227
  hi: new xdr.Int64(hi),
321
228
  lo: new xdr.Uint64(lo),
@@ -331,19 +238,13 @@ function capError(code, message) {
331
238
  // u32 boundary - the same constant the contract decodes with. A value above
332
239
  // this either overflows during encode or is refused at install.
333
240
  const U32_MAX = 4294967295;
334
- // The maximum decimal basis an oracle threshold can declare (mirrors
335
- // `MAX_ORACLE_THRESHOLD_DECIMALS` in dsl.rs). A value above this is refused
336
- // at install with `ORACLE_PARAMS_OUT_OF_RANGE`.
337
- const MAX_ORACLE_THRESHOLD_DECIMALS = 18;
338
- /** Walk a `PredicateNode` and fail-closed on any leaf whose cap-set value the
339
- * contract would refuse at install. Mirrors `validate_scaled_ratios` in
340
- * dsl.rs:661-704 plus the broader cap-set gate (`literal_u32`, `literal_i128`
341
- * positivity, `literal_bytes` hex even-length, `oracle_threshold` decimals
342
- * range, `call_arg_scaled` positive-ratio). Defense in depth so the TS
343
- * self-verify pipeline rejects the same shapes Rust install already
344
- * refuses - a hand-crafted predicate that simulate/verify green-lights must
345
- * NOT be installable. Throws `MALFORMED_PREDICATE` so the envelope shapes
346
- * it into a ToolError. */
241
+ /** Walk a `PredicateNode` and fail-closed on any leaf whose value the contract
242
+ * would refuse at install: the argument selectors carry u32 indices, so a
243
+ * value outside that range is refused here rather than on chain. Defense in
244
+ * depth so the TS self-verify pipeline rejects the same shapes Rust install
245
+ * already refuses - a hand-crafted predicate that simulate/verify
246
+ * green-lights must NOT be installable. Throws `MALFORMED_PREDICATE` so the
247
+ * envelope shapes it into a ToolError. */
347
248
  function validateLeafValues(node) {
348
249
  function walkLeaf(leaf, path) {
349
250
  switch (leaf.kind) {
@@ -365,107 +266,22 @@ function validateLeafValues(node) {
365
266
  throw malformed(`call_arg_field.element out of u32 range at ${path}`);
366
267
  }
367
268
  return;
368
- case 'call_arg_scaled': {
369
- if (!Number.isInteger(leaf.index) || leaf.index < 0 || leaf.index > U32_MAX) {
370
- throw malformed(`call_arg_scaled.index out of u32 range at ${path}`);
371
- }
372
- // num / den are i128 on chain, decimal strings on the wire. The
373
- // contract refuses `den == 0` and `num <= 0` / `den <= 0` at install
374
- // (dsl.rs:664-672); mirror that here so a future regression in
375
- // `validate_scaled_ratios` cannot let a divide-by-zero policy reach
376
- // the wire. BigInt throws on non-numeric strings -> malformed.
377
- let num;
378
- let den;
379
- try {
380
- num = BigInt(leaf.num);
381
- den = BigInt(leaf.den);
382
- }
383
- catch {
384
- throw malformed(`call_arg_scaled.num/den not a decimal integer at ${path}`);
385
- }
386
- if (num <= 0n)
387
- throw malformed(`call_arg_scaled.num must be > 0 at ${path}`);
388
- if (den <= 0n)
389
- throw malformed(`call_arg_scaled.den must be > 0 at ${path}`);
390
- return;
391
- }
392
- case 'literal_u32':
393
- if (!Number.isInteger(leaf.value) || leaf.value < 0 || leaf.value > U32_MAX) {
394
- throw malformed(`literal_u32.value out of u32 range at ${path}`);
395
- }
396
- return;
397
- case 'literal_i128':
398
- // literal_i128 is signed; the contract allows negatives (i128
399
- // arithmetic), but caps on a positive quantity (amount / window
400
- // bound) should never be negative - a negative cap is silently
401
- // satisfied by every non-negative amount. The contract gate is
402
- // already on the leaf's ROLE (amount vs equality) not the value;
403
- // here we mirror the value-only invariant the cap-set gate enforces
404
- // by refusing the syntactic shape that would clearly be a bug
405
- // (literal_i128 as a CAP with a leading `-` on a non-equality).
406
- // We do not gate equality i128 - `literal_i128` as an address-by-
407
- // equality is fine (it is just a constant).
408
- // The value itself is always accepted; the structural check
409
- // (non-negative for an amount / window bound) is left to the
410
- // caller-built predicate, not the encoder.
411
- return;
412
- case 'literal_bytes':
413
- // Hex even-length: a non-hex char silently drops, and an odd
414
- // length yields a half-byte Buffer. The contract decodes with a
415
- // strict hex parser and refuses anything that is not even-length
416
- // hex; mirror that here.
417
- if (!/^[0-9a-fA-F]*$/.test(leaf.value) || leaf.value.length % 2 !== 0) {
418
- throw malformed(`literal_bytes.value must be even-length hex at ${path}`);
419
- }
420
- return;
421
- case 'oracle_threshold':
422
- if (!Number.isInteger(leaf.decimals) ||
423
- leaf.decimals < 0 ||
424
- leaf.decimals > MAX_ORACLE_THRESHOLD_DECIMALS) {
425
- throw malformed(`oracle_threshold.decimals out of range (0..${MAX_ORACLE_THRESHOLD_DECIMALS}) at ${path}`);
426
- }
427
- return;
428
- case 'literal_vec':
429
- leaf.elements.forEach((e, i) => {
430
- walkLeaf(e, `${path}.elements[${i}]`);
431
- });
432
- return;
433
- // Selector and other leaves carry no cap-set values; the call_arg
434
- // branches above cover indices, the literal branches cover typed
435
- // constants. amount / window_spent / invocation_count / now /
436
- // valid_until / call_contract / call_fn / literal_address /
437
- // literal_symbol / literal_u64 / oracle_price are all value-free
438
- // at this gate.
439
- case 'amount':
440
- case 'window_spent':
441
- case 'invocation_count_in_window':
442
- case 'now':
443
- case 'valid_until':
444
269
  case 'call_contract':
445
270
  case 'call_fn':
446
271
  case 'literal_address':
447
272
  case 'literal_symbol':
448
- case 'literal_u64':
449
- case 'oracle_price':
450
273
  return;
451
274
  }
452
275
  }
453
276
  function walkNode(n, path) {
454
277
  switch (n.op) {
455
278
  case 'and':
456
- case 'or':
457
279
  n.children.forEach((c, i) => {
458
280
  walkNode(c, `${path}.children[${i}]`);
459
281
  });
460
282
  return;
461
- case 'not':
462
- walkNode(n.child, `${path}.child`);
463
- return;
464
283
  case 'eq':
465
- case 'lt':
466
284
  case 'lte':
467
- case 'gt':
468
- case 'gte':
469
285
  walkLeaf(n.left, `${path}.left`);
470
286
  walkLeaf(n.right, `${path}.right`);
471
287
  return;
@@ -17,15 +17,9 @@ export function jsonToAst(value) {
17
17
  const v = value;
18
18
  switch (v.op) {
19
19
  case 'and':
20
- case 'or':
21
- return { op: v.op, children: arrayOf(v.children, jsonToAst) };
22
- case 'not':
23
- return { op: 'not', child: jsonToAst(v.child) };
20
+ return { op: 'and', children: arrayOf(v.children, jsonToAst) };
24
21
  case 'eq':
25
- case 'lt':
26
22
  case 'lte':
27
- case 'gt':
28
- case 'gte':
29
23
  return { op: v.op, left: jsonToLeaf(v.left), right: jsonToLeaf(v.right) };
30
24
  case 'in':
31
25
  return { op: 'in', needle: jsonToLeaf(v.needle), haystack: arrayOf(v.haystack, jsonToLeaf) };
@@ -45,42 +39,11 @@ function jsonToLeaf(value) {
45
39
  switch (v.kind) {
46
40
  case 'call_contract':
47
41
  case 'call_fn':
48
- case 'now':
49
- case 'valid_until':
50
42
  return { kind: v.kind };
51
43
  case 'call_arg':
52
44
  return { kind: 'call_arg', index: numberField(v, 'index') };
53
45
  case 'call_arg_len':
54
46
  return { kind: 'call_arg_len', index: numberField(v, 'index') };
55
- // num/den are strings, not numbers: they are i128 on chain, and a JSON
56
- // number would silently lose precision past 2^53 before the encoder
57
- // ever saw the value.
58
- case 'call_arg_scaled':
59
- return {
60
- kind: 'call_arg_scaled',
61
- index: numberField(v, 'index'),
62
- num: stringField(v, 'num'),
63
- den: stringField(v, 'den'),
64
- };
65
- case 'call_arg_field':
66
- return {
67
- kind: 'call_arg_field',
68
- index: numberField(v, 'index'),
69
- element: numberField(v, 'element'),
70
- field: stringField(v, 'field'),
71
- };
72
- case 'amount':
73
- return { kind: 'amount', token: stringField(v, 'token') };
74
- case 'window_spent':
75
- return {
76
- kind: 'window_spent',
77
- token: stringField(v, 'token'),
78
- windowSeconds: numberField(v, 'windowSeconds'),
79
- };
80
- case 'invocation_count_in_window':
81
- return { kind: 'invocation_count_in_window', windowSecs: numberField(v, 'windowSecs') };
82
- case 'oracle_price':
83
- return { kind: 'oracle_price', asset: stringField(v, 'asset') };
84
47
  case 'literal_address':
85
48
  return { kind: 'literal_address', value: stringField(v, 'value') };
86
49
  case 'literal_i128':
@@ -89,10 +52,6 @@ function jsonToLeaf(value) {
89
52
  return { kind: 'literal_symbol', value: stringField(v, 'value') };
90
53
  case 'literal_u32':
91
54
  return { kind: 'literal_u32', value: numberField(v, 'value') };
92
- case 'literal_u64':
93
- return { kind: 'literal_u64', value: stringField(v, 'value') };
94
- case 'literal_bytes':
95
- return { kind: 'literal_bytes', value: stringField(v, 'value') };
96
55
  case 'literal_vec':
97
56
  return { kind: 'literal_vec', elements: arrayOf(v.elements, jsonToLeaf) };
98
57
  default:
@@ -325,14 +325,9 @@ function recordInvocation(contract, fn, args, network, knownSet, knownContractSe
325
325
  // parseConfidence denominator isn't inflated by repeat calls), but each
326
326
  // unrecognised invocation is recorded separately so the numerator
327
327
  // reflects every fail-closed hit.
328
- if (knownSet.has(contract)) {
329
- if (!knownContractSet.has(contract)) {
330
- knownContractSet.add(contract);
331
- knownOut.push(contract);
332
- }
333
- return;
334
- }
335
- if (identifyProtocol(contract, fn, args, network ?? undefined)) {
328
+ // `||` short-circuits, so `identifyProtocol` is still only consulted when the
329
+ // contract is not already in `knownSet` - the same call order as before.
330
+ if (knownSet.has(contract) || identifyProtocol(contract, fn, args, network ?? undefined)) {
336
331
  if (!knownContractSet.has(contract)) {
337
332
  knownContractSet.add(contract);
338
333
  knownOut.push(contract);
@@ -16,15 +16,21 @@ export declare function isBelowThreshold(c: ParseConfidence): boolean;
16
16
  * under-confidence recording.
17
17
  *
18
18
  * The remediation text branches on which diagnostic bucket is non-empty:
19
- * - unknownContracts only -> user MUST supply an ABI (or re-capture
20
- * against a known protocol version). The contract is real, the
21
- * recorder just cannot decode it.
22
- * - opaqueScVals only -> the recorder encountered a value shape it
23
- * should support but did not decode. The user cannot supply an ABI
24
- * to fix a decoder bug; the guidance explicitly says so and points at
25
- * re-running after a tool upgrade / reporting the path.
26
- * - both -> list both diagnostics AND chain the right remediation for
27
- * each (the user supplies an ABI AND reports the decoder gap).
28
- * - neither (the "denom === 0" path) -> unchanged from the pre-fix
29
- * text; the user did not actually hit a code-level barrier. */
19
+ * - unknownContracts only -> the contract is not in the compiled-in
20
+ * registry. NOT "the contract has no interface": most Soroban
21
+ * contracts publish a typed spec on chain, this package just never
22
+ * reads one. The reason code stays `no-abi` because it is a published
23
+ * enum value, so the TEXT has to carry the correction.
24
+ * - opaqueScVals only -> the recorder met a value shape it should
25
+ * support but did not decode. That is a decoder bug, and the guidance
26
+ * says so rather than implying the caller can fix it.
27
+ * - both -> list both diagnostics and chain the right remediation.
28
+ * - neither (the "denom === 0" path) -> the caller did not actually hit
29
+ * a code-level barrier.
30
+ *
31
+ * Do NOT tell the caller to supply an ABI. There is no input for one:
32
+ * recognition runs off the compiled-in registry plus a known-contract set
33
+ * that no public tool boundary exposes. The old text named an action
34
+ * nobody could take and sent users looking for a file they do not have and
35
+ * would not need. */
30
36
  export declare function buildLowConfidenceQuestion(c: ParseConfidence): string;