@crediolabs/policy-synth 0.1.18 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +6 -5
  2. package/dist/adapters/interpreter/adapter.d.ts +19 -25
  3. package/dist/adapters/interpreter/adapter.js +54 -487
  4. package/dist/adapters/interpreter/index.d.ts +1 -1
  5. package/dist/adapters/interpreter/index.js +1 -1
  6. package/dist/adapters/oz/adapter.js +1 -15
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/index.d.ts +1 -4
  9. package/dist/index.js +1 -4
  10. package/dist/install/build-add-context-rule.d.ts +3 -11
  11. package/dist/install/build-add-context-rule.js +2 -97
  12. package/dist/install/build-install-policy.d.ts +4 -53
  13. package/dist/install/build-install-policy.js +44 -123
  14. package/dist/ir/index.d.ts +1 -1
  15. package/dist/ir/types.d.ts +7 -76
  16. package/dist/ir/types.js +0 -2
  17. package/dist/predicate/decode.d.ts +1 -1
  18. package/dist/predicate/decode.js +2 -70
  19. package/dist/predicate/encode.js +45 -229
  20. package/dist/predicate/from-json.js +1 -42
  21. package/dist/record/decode.js +3 -8
  22. package/dist/review-card/builder.d.ts +4 -3
  23. package/dist/review-card/builder.js +18 -162
  24. package/dist/review-card/cross-check.js +2 -105
  25. package/dist/review-card/render-leaf.d.ts +4 -0
  26. package/dist/review-card/render-leaf.js +47 -0
  27. package/dist/run/index.d.ts +35 -53
  28. package/dist/run/index.js +115 -156
  29. package/dist/run/schemas.d.ts +168 -1650
  30. package/dist/run/schemas.js +36 -199
  31. package/dist/seams/types.d.ts +0 -2
  32. package/dist/simulate/deny-cases.d.ts +26 -0
  33. package/dist/simulate/deny-cases.js +282 -0
  34. package/dist/simulate/evaluate.d.ts +18 -0
  35. package/dist/simulate/evaluate.js +271 -0
  36. package/dist/simulate/index.d.ts +4 -0
  37. package/dist/simulate/index.js +8 -0
  38. package/dist/synth/compose-from-recording.d.ts +24 -51
  39. package/dist/synth/compose-from-recording.js +120 -282
  40. package/dist/synth/deny-cases.d.ts +5 -7
  41. package/dist/synth/deny-cases.js +23 -373
  42. package/dist/synth/evaluate.d.ts +0 -21
  43. package/dist/synth/evaluate.js +56 -336
  44. package/dist/synth/index.d.ts +0 -5
  45. package/dist/synth/index.js +0 -5
  46. package/dist/synth/permit-context.d.ts +2 -9
  47. package/dist/synth/permit-context.js +1 -82
  48. package/dist/synth/synthesize-from-recording.d.ts +1 -29
  49. package/dist/synth/synthesize-from-recording.js +41 -356
  50. package/dist/types.d.ts +8 -60
  51. package/dist/types.js +5 -2
  52. package/dist/verify/simulate.d.ts +1 -14
  53. package/dist/verify/simulate.js +2 -88
  54. package/dist/verify/verify.d.ts +1 -7
  55. package/dist/verify/verify.js +0 -8
  56. package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
  57. package/dist-cjs/adapters/interpreter/adapter.js +56 -489
  58. package/dist-cjs/adapters/interpreter/index.d.ts +1 -1
  59. package/dist-cjs/adapters/interpreter/index.js +1 -2
  60. package/dist-cjs/adapters/oz/adapter.js +1 -15
  61. package/dist-cjs/errors.d.ts +1 -1
  62. package/dist-cjs/index.d.ts +1 -4
  63. package/dist-cjs/index.js +1 -4
  64. package/dist-cjs/install/build-add-context-rule.d.ts +3 -11
  65. package/dist-cjs/install/build-add-context-rule.js +1 -96
  66. package/dist-cjs/install/build-install-policy.d.ts +4 -53
  67. package/dist-cjs/install/build-install-policy.js +44 -122
  68. package/dist-cjs/ir/index.d.ts +1 -1
  69. package/dist-cjs/ir/types.d.ts +7 -76
  70. package/dist-cjs/ir/types.js +0 -2
  71. package/dist-cjs/predicate/decode.d.ts +1 -1
  72. package/dist-cjs/predicate/decode.js +2 -70
  73. package/dist-cjs/predicate/encode.js +45 -229
  74. package/dist-cjs/predicate/from-json.js +1 -42
  75. package/dist-cjs/record/decode.js +3 -8
  76. package/dist-cjs/review-card/builder.d.ts +4 -3
  77. package/dist-cjs/review-card/builder.js +23 -167
  78. package/dist-cjs/review-card/cross-check.js +7 -110
  79. package/dist-cjs/review-card/render-leaf.d.ts +4 -0
  80. package/dist-cjs/review-card/render-leaf.js +52 -0
  81. package/dist-cjs/run/index.d.ts +35 -53
  82. package/dist-cjs/run/index.js +116 -162
  83. package/dist-cjs/run/schemas.d.ts +168 -1650
  84. package/dist-cjs/run/schemas.js +37 -200
  85. package/dist-cjs/seams/types.d.ts +0 -2
  86. package/dist-cjs/simulate/deny-cases.d.ts +26 -0
  87. package/dist-cjs/simulate/deny-cases.js +286 -0
  88. package/dist-cjs/simulate/evaluate.d.ts +18 -0
  89. package/dist-cjs/simulate/evaluate.js +274 -0
  90. package/dist-cjs/simulate/index.d.ts +4 -0
  91. package/dist-cjs/simulate/index.js +13 -0
  92. package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
  93. package/dist-cjs/synth/compose-from-recording.js +120 -282
  94. package/dist-cjs/synth/deny-cases.d.ts +5 -7
  95. package/dist-cjs/synth/deny-cases.js +23 -374
  96. package/dist-cjs/synth/evaluate.d.ts +0 -21
  97. package/dist-cjs/synth/evaluate.js +57 -337
  98. package/dist-cjs/synth/index.d.ts +0 -5
  99. package/dist-cjs/synth/index.js +1 -11
  100. package/dist-cjs/synth/permit-context.d.ts +2 -9
  101. package/dist-cjs/synth/permit-context.js +1 -82
  102. package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
  103. package/dist-cjs/synth/synthesize-from-recording.js +39 -354
  104. package/dist-cjs/types.d.ts +8 -60
  105. package/dist-cjs/types.js +6 -3
  106. package/dist-cjs/verify/simulate.d.ts +1 -14
  107. package/dist-cjs/verify/simulate.js +2 -88
  108. package/dist-cjs/verify/verify.d.ts +1 -7
  109. package/dist-cjs/verify/verify.js +0 -8
  110. package/package.json +4 -4
  111. package/src/adapters/interpreter/adapter.ts +76 -572
  112. package/src/errors.ts +0 -19
  113. package/src/index.ts +1 -4
  114. package/src/install/build-add-context-rule.ts +4 -127
  115. package/src/install/build-install-policy.ts +93 -214
  116. package/src/predicate/decode.ts +2 -70
  117. package/src/predicate/encode.ts +49 -261
  118. package/src/predicate/from-json.ts +1 -43
  119. package/src/record/decode.ts +3 -8
  120. package/src/review-card/builder.ts +19 -168
  121. package/src/review-card/cross-check.ts +3 -105
  122. package/src/review-card/render-leaf.ts +48 -0
  123. package/src/run/index.ts +130 -186
  124. package/src/run/schemas.ts +38 -214
  125. package/src/simulate/deny-cases.ts +334 -0
  126. package/src/simulate/evaluate.ts +284 -0
  127. package/src/simulate/index.ts +11 -0
  128. package/src/synth/compose-from-recording.ts +148 -347
  129. package/src/synth/index.ts +0 -13
  130. package/src/synth/synthesize-from-recording.ts +43 -456
  131. package/src/types.ts +13 -49
  132. package/dist/install/build-install-predicate.d.ts +0 -96
  133. package/dist/install/build-install-predicate.js +0 -444
  134. package/dist-cjs/install/build-install-predicate.d.ts +0 -96
  135. package/dist-cjs/install/build-install-predicate.js +0 -479
  136. package/src/adapters/interpreter/index.ts +0 -8
  137. package/src/adapters/oz/adapter.ts +0 -376
  138. package/src/adapters/oz/index.ts +0 -9
  139. package/src/codegen/compile-gate.ts +0 -167
  140. package/src/codegen/index.ts +0 -17
  141. package/src/codegen/template.ts +0 -165
  142. package/src/ir/index.ts +0 -13
  143. package/src/ir/types.ts +0 -132
  144. package/src/mandate/index.ts +0 -4
  145. package/src/mandate/to-ir.ts +0 -71
  146. package/src/mandate/types.ts +0 -21
  147. package/src/seams/index.ts +0 -11
  148. package/src/seams/types.ts +0 -81
  149. package/src/synth/deny-cases.ts +0 -663
  150. package/src/synth/evaluate.ts +0 -613
  151. package/src/synth/harness.ts +0 -68
  152. package/src/synth/minimize.ts +0 -48
  153. package/src/synth/permit-context.ts +0 -136
  154. package/src/synth/predicate-literals.ts +0 -27
  155. package/src/synth/synthesize-from-mandate.ts +0 -82
  156. package/src/verify/envelope.ts +0 -28
  157. package/src/verify/index.ts +0 -5
  158. package/src/verify/simulate.ts +0 -311
  159. package/src/verify/verify.ts +0 -243
@@ -1,145 +1,51 @@
1
1
  "use strict";
2
- // src/synth/evaluate.ts - the model evaluator (TS model of interpreter semantics).
2
+ // src/synth/evaluate.ts - TypeScript reference evaluator for grammar version 3.
3
3
  //
4
4
  // Pure function. Determinism: same `(predicate, ctx)` -> byte-identical result,
5
5
  // no clock, no randomness. Deny order (deny on FIRST violation, stable reason):
6
- // 1. ledger expiry -> 'EXPIRED'
7
- // 2. `now` past valid_until -> 'EXPIRED'
8
- // 3. contract mismatch -> 'CONTRACT_SCOPE'
9
- // 4. per-ScVal equality on fn/args; EXACT ordered vector
10
- // equality; fail-closed on opaque args -> 'ARG_MISMATCH' / 'FN_MISMATCH'
11
- // 5. `in` membership; empty haystack ALWAYS denies -> 'NOT_IN_ALLOWLIST'
12
- // 6. amount / window_spent via BigInt -> 'AMOUNT_BOUND'
13
- // 7. invocation_count_in_window -> 'FREQUENCY'
14
- // 8. oracle_price: fatal -> throw OracleError, deny with
15
- // underlying ORACLE_* reason -> 'ORACLE_*'
16
- // 9. boolean nodes (and/or/not)
17
- // 10. signer threshold gate -> 'THRESHOLD_NOT_MET'
18
- // 11. otherwise permit.
6
+ // 1. contract mismatch -> 'CONTRACT_SCOPE'
7
+ // 2. per-ScVal equality on fn/args; EXACT ordered vector
8
+ // equality; fail-closed on opaque args -> 'ARG_MISMATCH'
9
+ // 3. `in` membership; empty haystack ALWAYS denies -> 'NOT_IN_ALLOWLIST'
10
+ // 4. otherwise permit.
19
11
  //
20
- // Amounts: BigInt on decimal strings. Oracle errors thrown (never
21
- // boolean-false `not`/`or` could mask).
12
+ // Grammar version 3 nodes: and, eq, lte
13
+ // Grammar version 3 leaves: call_contract, call_fn, call_arg(i),
14
+ // call_arg_len(i), call_arg_field(i, element, field),
15
+ // literal_address, literal_i128, literal_symbol, literal_u32, literal_vec
16
+ // Grammar version 3 deny reasons: ARG_MISMATCH, CONTRACT_SCOPE,
17
+ // UNSUPPORTED_NODE, NOT_IN_ALLOWLIST
22
18
  Object.defineProperty(exports, "__esModule", { value: true });
23
19
  exports.evaluate = evaluate;
24
- const predicate_literals_ts_1 = require("./predicate-literals.js");
25
- /** Oracle prices normalise to this many decimals; mirrors NORMALISED_DECIMALS
26
- * in oracle.rs. A threshold on any other basis must say so. */
27
- const NORMALISED_DECIMALS = 9;
28
- /** Mirrors MAX_ORACLE_THRESHOLD_DECIMALS in dsl.rs. */
29
- const MAX_ORACLE_THRESHOLD_DECIMALS = 18;
30
- /** Internal fatal thrown by the oracle path; caught at the top of `evaluate`
31
- * and converted to the matching `ORACLE_*` deny reason. NOT a boolean-false
32
- * that `not` / `or` could mask. */
33
- class OracleError extends Error {
34
- code;
35
- constructor(code) {
36
- super(`oracle fatal: ${code}`);
37
- this.code = code;
38
- }
39
- }
40
- const ORACLE_ERROR_CODES = {
41
- stale: 'ORACLE_STALE',
42
- missing: 'ORACLE_MISSING',
43
- deviation: 'ORACLE_DEVIATION_EXCEEDED',
44
- paused: 'ORACLE_PAUSED',
45
- decimals: 'ORACLE_DECIMALS_MISMATCH',
46
- fingerprint: 'ORACLE_FINGERPRINT_DRIFT',
47
- };
48
20
  /** Evaluate a `PredicateNode` against the candidate call described by `ctx`.
49
21
  * Pure function. Returns `{ permit: true }` or `{ permit: false; reason }`. */
50
22
  function evaluate(predicate, ctx) {
51
- try {
52
- // --- step 1: ledger-time expiry ---
53
- if (ctx.validUntilLedger !== undefined && ctx.atLedger > ctx.validUntilLedger) {
54
- return { permit: false, reason: 'EXPIRED' };
55
- }
56
- // --- step 2..9: predicate tree ---
57
- const decision = walk(predicate, ctx);
58
- // --- step 10: signer threshold gate (only when the predicate permitted) ---
59
- if (decision.permit && ctx.signerWeights !== undefined) {
60
- let totalWeight = 0n;
61
- for (const w of Object.values(ctx.signerWeights)) {
62
- // weights are non-negative integers; BigInt keeps the gate bounded
63
- totalWeight += BigInt(w);
64
- }
65
- if (totalWeight === 0n)
66
- return { permit: false, reason: 'THRESHOLD_NOT_MET' };
67
- }
68
- return decision;
69
- }
70
- catch (e) {
71
- if (e instanceof OracleError) {
72
- return { permit: false, reason: e.code };
73
- }
74
- throw e;
75
- }
23
+ return walk(predicate, ctx);
76
24
  }
77
25
  /** Walk the predicate tree. Returns the FIRST deny reason encountered on
78
- * the active branch (so `and` fails-fast; `or` accepts the first permit). */
26
+ * the active branch (so `and` fails-fast). */
79
27
  function walk(node, ctx) {
80
28
  switch (node.op) {
81
29
  case 'and': {
82
30
  let lastDeny = null;
83
31
  for (const child of node.children) {
84
32
  const r = walk(child, ctx);
85
- if (!r.permit) {
86
- // deny-on-first: short-circuit on the failing child.
33
+ if (!r.permit)
87
34
  return r;
88
- }
89
35
  lastDeny = r;
90
36
  }
91
37
  return lastDeny ?? { permit: true };
92
38
  }
93
- case 'or': {
94
- let lastDeny = null;
95
- for (const child of node.children) {
96
- const r = walk(child, ctx);
97
- if (r.permit)
98
- return r;
99
- lastDeny = r;
100
- }
101
- return lastDeny ?? { permit: false, reason: 'NOT_IN_ALLOWLIST' };
102
- }
103
- case 'not': {
104
- // `not` structurally inverts the child unless the child contains an
105
- // oracle leaf (compile-time rule: no oracle leaf under not/or). A
106
- // child `OracleError` is re-thrown to the catch at the top of `evaluate`.
107
- const r = walk(node.child, ctx);
108
- if (r.permit)
109
- return { permit: false, reason: 'FN_MISMATCH' };
110
- return { permit: true };
111
- }
112
39
  case 'eq':
113
- case 'lt':
114
40
  case 'lte':
115
- case 'gt':
116
- case 'gte':
117
41
  return evalCompare(node.op, node.left, node.right, ctx);
118
42
  case 'in':
119
43
  return evalIn(node.needle, node.haystack, ctx);
120
44
  }
121
45
  }
122
- /** Step 2..8: comparison leaf evaluation. */
46
+ /** Comparison leaf evaluation. */
123
47
  function evalCompare(op, left, right, ctx) {
124
- // --- step 2: `now` vs `valid_until` semantics ---
125
- if (left.kind === 'now' && right.kind === 'valid_until') {
126
- const expired = op === 'gt' || op === 'gte' ? ctx.nowSeconds >= 0 : ctx.nowSeconds < 0;
127
- // `valid_until` is modelled as a synthetic future timestamp far past
128
- // `nowSeconds` so the only true-positive expired path is the `gt`/`gte`
129
- // shapes callers actually write.
130
- if (op === 'gt' || op === 'gte') {
131
- if (ctx.nowSeconds > 0)
132
- return { permit: false, reason: 'EXPIRED' };
133
- }
134
- else if (op === 'lt' || op === 'lte') {
135
- return { permit: true };
136
- }
137
- return expired ? { permit: false, reason: 'EXPIRED' } : { permit: true };
138
- }
139
- if (right.kind === 'now' && left.kind === 'valid_until') {
140
- return evalCompare(op, right, left, ctx);
141
- }
142
- // --- step 3: CONTRACT_SCOPE on call_contract eq ---
48
+ // CONTRACT_SCOPE on call_contract eq
143
49
  if (left.kind === 'call_contract' && op === 'eq') {
144
50
  if (right.kind !== 'literal_address')
145
51
  return { permit: false, reason: 'CONTRACT_SCOPE' };
@@ -147,32 +53,20 @@ function evalCompare(op, left, right, ctx) {
147
53
  ? { permit: true }
148
54
  : { permit: false, reason: 'CONTRACT_SCOPE' };
149
55
  }
150
- // --- step 4a: call_fn equality ---
56
+ // call_fn equality
151
57
  if (left.kind === 'call_fn' && op === 'eq') {
152
58
  if (right.kind !== 'literal_symbol')
153
- return { permit: false, reason: 'FN_MISMATCH' };
154
- return right.value === ctx.fn ? { permit: true } : { permit: false, reason: 'FN_MISMATCH' };
59
+ return { permit: false, reason: 'ARG_MISMATCH' };
60
+ return right.value === ctx.fn ? { permit: true } : { permit: false, reason: 'ARG_MISMATCH' };
155
61
  }
156
- // --- step 4b: call_arg comparison (eq / exact-vec, or an ordered numeric bound) ---
62
+ // call_arg comparison (eq / exact-vec, or lte ordered numeric bound)
157
63
  if (left.kind === 'call_arg') {
158
- // The swap's canonical form is `call_arg[out] >= call_arg_scaled(in, num, den)`.
159
- // Dispatched here so the floor's dedicated reason codes (ARITHMETIC_OVERFLOW
160
- // -> SLIPPAGE_FLOOR) reach the user; routing the scaled RHS through the
161
- // generic `evalArgOrderedCompare` would mask overflow as ARG_MISMATCH.
162
- if (right.kind === 'call_arg_scaled') {
163
- return evalScaledArgCompare(op, left, right, ctx);
164
- }
165
64
  const actual = ctx.args[left.index];
166
65
  if (op !== 'eq')
167
66
  return evalArgOrderedCompare(op, actual, right);
168
67
  return evalArgEq(op, actual, right, ctx);
169
68
  }
170
- // --- step 4b': scaled leaf on the LEFT (`call_arg_scaled(in, num, den) <= call_arg[out]`).
171
- // Symmetric form so a policy that phrases the floor either way works.
172
- if (left.kind === 'call_arg_scaled') {
173
- return evalScaledArgCompare(op, right, left, ctx);
174
- }
175
- // --- step 4c: call_arg_len: length of a vec-typed argument as u32.
69
+ // call_arg_len: length of a vec-typed argument as u32.
176
70
  // Fails closed on a non-vec / absent arg or a non-u32 literal.
177
71
  if (left.kind === 'call_arg_len') {
178
72
  const actual = ctx.args[left.index];
@@ -184,7 +78,7 @@ function evalCompare(op, left, right, ctx) {
184
78
  ? { permit: true }
185
79
  : { permit: false, reason: 'ARG_MISMATCH' };
186
80
  }
187
- // --- step 4d: call_arg_field: value of a field in the map at element i of
81
+ // call_arg_field: value of a field in the map at element i of
188
82
  // the vec at argument index. Fails closed on shape / type / range issues.
189
83
  if (left.kind === 'call_arg_field') {
190
84
  const actual = ctx.args[left.index];
@@ -202,34 +96,18 @@ function evalCompare(op, left, right, ctx) {
202
96
  return evalArgEq(op, entry.val, right, ctx);
203
97
  return evalArgOrderedCompare(op, entry.val, right);
204
98
  }
205
- // --- step 6: AMOUNT_BOUND ---
206
- if (left.kind === 'amount' && op !== 'eq') {
207
- return evalAmountCompare(op, left.token, right, ctx, 'amountByToken');
208
- }
209
- if (left.kind === 'window_spent' && op !== 'eq') {
210
- return evalWindowSpentCompare(op, left.token, right, ctx);
211
- }
212
- // `eq` on amount / window_spent not defined as a bound - fall through.
213
- // --- step 7: FREQUENCY ---
214
- if (left.kind === 'invocation_count_in_window') {
215
- return evalFrequencyCompare(op, left.windowSecs, right, ctx);
216
- }
217
- // --- step 8: oracle_price (FATAL via throw) ---
218
- if (left.kind === 'oracle_price') {
219
- return evalOracleCompare(op, left.asset, right, ctx);
220
- }
221
99
  // Unknown leaf/op combination - structural fail-closed.
222
- return { permit: false, reason: 'FN_MISMATCH' };
100
+ return { permit: false, reason: 'UNSUPPORTED_NODE' };
223
101
  }
224
- /** Step 4b: per-ScVal equality. Handles literal_vec as an EXACT ordered
225
- * sequence: compare element-by-element in order; deny if length or any
226
- * element differs. Opaque args (`type: 'other'`) fail closed. */
227
- function evalArgEq(op, actual, right, ctx) {
102
+ /** Per-ScVal equality. Handles literal_vec as an EXACT ordered sequence:
103
+ * compare element-by-element in order; deny if length or any element differs.
104
+ * Opaque args (`type: 'other'`) fail closed. */
105
+ function evalArgEq(op, actual, right, _ctx) {
228
106
  // eq(call_arg[i], literal_vec) -> EXACT ordered vector equality.
229
107
  if (op === 'eq' && right.kind === 'literal_vec') {
230
108
  if (actual?.type !== 'vec')
231
109
  return { permit: false, reason: 'ARG_MISMATCH' };
232
- return compareVecExact(actual.value, right.elements, ctx)
110
+ return compareVecExact(actual.value, right.elements)
233
111
  ? { permit: true }
234
112
  : { permit: false, reason: 'ARG_MISMATCH' };
235
113
  }
@@ -263,112 +141,18 @@ function evalArgEq(op, actual, right, ctx) {
263
141
  ? { permit: true }
264
142
  : { permit: false, reason: 'ARG_MISMATCH' };
265
143
  }
266
- if (op === 'eq' && right.kind === 'literal_u64') {
267
- if (!actual)
268
- return { permit: false, reason: 'ARG_MISMATCH' };
269
- return actual.type === 'u64' && actual.value === right.value
270
- ? { permit: true }
271
- : { permit: false, reason: 'ARG_MISMATCH' };
272
- }
273
- if (op === 'eq' && right.kind === 'literal_bytes') {
274
- if (!actual)
275
- return { permit: false, reason: 'ARG_MISMATCH' };
276
- return actual.type === 'bytes' && actual.value === right.value
277
- ? { permit: true }
278
- : { permit: false, reason: 'ARG_MISMATCH' };
279
- }
280
144
  // Anything else: fail closed on opacity (cannot decode the arg reliably).
281
145
  if (!actual || actual.type === 'other')
282
146
  return { permit: false, reason: 'ARG_MISMATCH' };
283
147
  return { permit: false, reason: 'ARG_MISMATCH' };
284
148
  }
285
- /** Step 4b': slippage-floor comparison. Mirrors the Rust `eval_scaled_arg_compare`:
286
- * the scaled leaf is `args[index] * num / den` (truncating toward zero). On
287
- * overflow or divide-by-zero deny with `ARITHMETIC_OVERFLOW`; a failed bound
288
- * denies with `SLIPPAGE_FLOOR` (the dedicated reason) rather than the generic
289
- * `ARG_MISMATCH`. A scaled-on-scaled compare denies `ARG_MISMATCH`
290
- * (no definable semantics). The non-scaled operand must be numeric
291
- * (`call_arg` carrying a number, or a numeric literal); else `ARG_MISMATCH`. */
292
- function evalScaledArgCompare(op, left, right, ctx) {
293
- let scaled;
294
- let other;
295
- let scaledOnRight;
296
- if (left.kind === 'call_arg_scaled') {
297
- scaled = left;
298
- other = right;
299
- scaledOnRight = false;
300
- }
301
- else if (right.kind === 'call_arg_scaled') {
302
- scaled = right;
303
- other = left;
304
- scaledOnRight = true;
305
- }
306
- else {
307
- // Programming error in the dispatcher; fail closed.
308
- return { permit: false, reason: 'ARG_MISMATCH' };
309
- }
310
- // Out-of-bounds or non-numeric arg fails closed as ARG_MISMATCH (not
311
- // SLIPPAGE_FLOOR) - a violated floor is the wrong code when the operand
312
- // itself could not be read.
313
- if (scaled.index >= ctx.args.length) {
314
- return { permit: false, reason: 'ARG_MISMATCH' };
315
- }
316
- const input = argNumericBigInt(ctx.args[scaled.index]);
317
- if (input === null)
318
- return { permit: false, reason: 'ARG_MISMATCH' };
319
- let num;
320
- let den;
321
- try {
322
- num = BigInt(scaled.num);
323
- den = BigInt(scaled.den);
324
- }
325
- catch {
326
- return { permit: false, reason: 'ARG_MISMATCH' };
327
- }
328
- // Install refuses `den == 0` and `num <= 0` / `den <= 0`. The runtime
329
- // check is defensive belt-and-braces so a validator regression cannot
330
- // panic the frame on a divide-by-zero.
331
- if (den === 0n)
332
- return { permit: false, reason: 'ARITHMETIC_OVERFLOW' };
333
- const product = input * num;
334
- let scaledValue;
335
- try {
336
- // BigInt division truncates toward zero (matches Rust `i128::checked_div`).
337
- scaledValue = product / den;
338
- }
339
- catch {
340
- return { permit: false, reason: 'ARITHMETIC_OVERFLOW' };
341
- }
342
- // i128 range check: the contract would have wrapped on i128 arithmetic;
343
- // surface the same deny.
344
- const I128_MAX = (1n << 127n) - 1n;
345
- const I128_MIN = -(1n << 127n);
346
- if (scaledValue > I128_MAX || scaledValue < I128_MIN) {
347
- return { permit: false, reason: 'ARITHMETIC_OVERFLOW' };
348
- }
349
- let otherVal;
350
- if (other.kind === 'call_arg') {
351
- otherVal = argNumericBigInt(ctx.args[other.index]);
352
- }
353
- else {
354
- otherVal = (0, predicate_literals_ts_1.literalNumericBigInt)(other);
355
- }
356
- if (otherVal === null)
357
- return { permit: false, reason: 'ARG_MISMATCH' };
358
- const pass = scaledOnRight
359
- ? bigintCmp(op, otherVal.toString(), scaledValue.toString())
360
- : bigintCmp(op, scaledValue.toString(), otherVal.toString());
361
- return pass ? { permit: true } : { permit: false, reason: 'SLIPPAGE_FLOOR' };
362
- }
363
- /** Ordered numeric comparison (lt/lte/gt/gte) on a `call_arg`. The interpreter
364
- * reads the arg as an integer (i128 / u64 / u32 on the recorder's ScVal
365
- * surface) and compares it to a numeric literal via BigInt. A non-numeric arg
366
- * or a non-numeric literal fails closed (ARG_MISMATCH) rather than permitting
367
- * an undecidable bound. Backs the SoroSwap input-amount cap
368
- * (`call_arg[0] <= limit`). */
149
+ /** Ordered numeric comparison (lte) on a `call_arg`. The interpreter reads the
150
+ * arg as an integer (i128 on the recorder's ScVal surface) and compares it
151
+ * to a numeric literal via BigInt. A non-numeric arg or a non-numeric literal
152
+ * fails closed (ARG_MISMATCH) rather than permitting an undecidable bound. */
369
153
  function evalArgOrderedCompare(op, actual, right) {
370
154
  const actualInt = argNumericBigInt(actual);
371
- const literalInt = (0, predicate_literals_ts_1.literalNumericBigInt)(right);
155
+ const literalInt = literalNumericBigInt(right);
372
156
  if (actualInt === null || literalInt === null) {
373
157
  return { permit: false, reason: 'ARG_MISMATCH' };
374
158
  }
@@ -376,12 +160,12 @@ function evalArgOrderedCompare(op, actual, right) {
376
160
  ? { permit: true }
377
161
  : { permit: false, reason: 'ARG_MISMATCH' };
378
162
  }
379
- /** BigInt value of a numeric-integer ScVal arg (i128 / u64 / u32), or null when
163
+ /** BigInt value of a numeric-integer ScVal arg (i128 / u32), or null when
380
164
  * the arg is absent, opaque, or a non-numeric type. */
381
165
  function argNumericBigInt(actual) {
382
166
  if (!actual)
383
167
  return null;
384
- if (actual.type === 'i128' || actual.type === 'u64' || actual.type === 'u32') {
168
+ if (actual.type === 'i128' || actual.type === 'u32') {
385
169
  try {
386
170
  return BigInt(actual.value);
387
171
  }
@@ -391,10 +175,27 @@ function argNumericBigInt(actual) {
391
175
  }
392
176
  return null;
393
177
  }
178
+ /** BigInt value of a numeric-integer literal leaf (`literal_i128` / `literal_u32`),
179
+ * or null for a non-numeric literal. Callers fail closed on null. */
180
+ function literalNumericBigInt(leaf) {
181
+ switch (leaf.kind) {
182
+ case 'literal_i128':
183
+ try {
184
+ return BigInt(leaf.value);
185
+ }
186
+ catch {
187
+ return null;
188
+ }
189
+ case 'literal_u32':
190
+ return BigInt(leaf.value);
191
+ default:
192
+ return null;
193
+ }
194
+ }
394
195
  /** Element-by-element ordered comparison of an `ScVal[]` against a
395
196
  * `PredicateLeaf[]`. Equal-length and equal at every index => permit.
396
197
  * Length mismatch OR any element mismatch => deny ARG_MISMATCH. */
397
- function compareVecExact(actual, expected, ctx) {
198
+ function compareVecExact(actual, expected) {
398
199
  if (actual.length !== expected.length)
399
200
  return false;
400
201
  for (let i = 0; i < expected.length; i++) {
@@ -402,14 +203,13 @@ function compareVecExact(actual, expected, ctx) {
402
203
  const a = actual[i];
403
204
  if (!e || !a)
404
205
  return false;
405
- const r = evalArgEq('eq', a, e, ctx);
206
+ const r = evalArgEq('eq', a, e, {});
406
207
  if (!r.permit)
407
208
  return false;
408
209
  }
409
210
  return true;
410
211
  }
411
- /** Step 5: `in` membership. Empty haystack ALWAYS denies. Opaque needle
412
- * fails closed. */
212
+ /** `in` membership. Empty haystack ALWAYS denies. Opaque needle fails closed. */
413
213
  function evalIn(needle, haystack, ctx) {
414
214
  if (haystack.length === 0)
415
215
  return { permit: false, reason: 'NOT_IN_ALLOWLIST' };
@@ -449,14 +249,6 @@ function resolveLeaf(leaf, ctx) {
449
249
  const entry = element.value.find((e) => e.key === leaf.field);
450
250
  return entry ? entry.val : undefined;
451
251
  }
452
- case 'amount':
453
- case 'window_spent':
454
- case 'oracle_price':
455
- case 'invocation_count_in_window':
456
- case 'now':
457
- case 'valid_until':
458
- case 'call_arg_scaled':
459
- return undefined; // selector leaves with no ScVal projection
460
252
  case 'literal_address':
461
253
  return { type: 'address', value: leaf.value };
462
254
  case 'literal_i128':
@@ -465,90 +257,18 @@ function resolveLeaf(leaf, ctx) {
465
257
  return { type: 'symbol', value: leaf.value };
466
258
  case 'literal_u32':
467
259
  return { type: 'u32', value: String(leaf.value) };
468
- case 'literal_u64':
469
- return { type: 'u64', value: leaf.value };
470
- case 'literal_bytes':
471
- return { type: 'bytes', value: leaf.value };
472
260
  case 'literal_vec':
473
261
  return undefined;
474
262
  }
475
263
  }
476
- /** Step 6: amount / window_spent compare on BigInt. Both leaves compare a
477
- * per-token BigInt record (current vs. rolling) to an i128 literal; the
478
- * compare + denial reason are identical, so a single helper handles both. */
479
- function evalAmountCompare(op, token, right, ctx, record) {
480
- const literal = right.kind === 'literal_i128' ? right.value : null;
481
- if (literal === null)
482
- return { permit: false, reason: 'AMOUNT_BOUND' };
483
- const actual = ctx[record][token] ?? '0';
484
- return bigintCmp(op, actual, literal)
485
- ? { permit: true }
486
- : { permit: false, reason: 'AMOUNT_BOUND' };
487
- }
488
- /** Step 6: window_spent compare on BigInt. */
489
- function evalWindowSpentCompare(op, token, right, ctx) {
490
- return evalAmountCompare(op, token, right, ctx, 'windowSpentByToken');
491
- }
492
- /** Step 7: invocation_count_in_window compare. */
493
- function evalFrequencyCompare(op, windowSecs, right, ctx) {
494
- const literal = right.kind === 'literal_u32' ? String(right.value) : null;
495
- if (literal === null)
496
- return { permit: false, reason: 'FREQUENCY' };
497
- const actual = String(ctx.invocationCountByWindow[windowSecs] ?? 0);
498
- return bigintCmp(op, actual, literal) ? { permit: true } : { permit: false, reason: 'FREQUENCY' };
499
- }
500
- /** Step 8: oracle_price compare. Reads `ctx.oraclePriceByAsset[asset]`. Any
501
- * error entry OR a missing key throws `OracleError` (FATAL). A satisfied
502
- * compare permits. */
503
- function evalOracleCompare(op, asset, right, ctx) {
504
- const entry = ctx.oraclePriceByAsset[asset];
505
- if (!entry)
506
- throw new OracleError('ORACLE_STALE');
507
- if ('error' in entry) {
508
- const mapped = ORACLE_ERROR_CODES[entry.error];
509
- if (!mapped)
510
- throw new OracleError('ORACLE_STALE');
511
- throw new OracleError(mapped);
512
- }
513
- // Mirrors eval_oracle_compare in dsl.rs. The threshold MUST declare its
514
- // decimal basis: prices are on the normalised 9-dp basis, and a bare literal
515
- // is refused (not assumed) because a raw 14-dp threshold that assumed the
516
- // 9-dp basis would permit everything.
517
- if (right.kind !== 'oracle_threshold')
518
- throw new OracleError('ORACLE_DECIMALS_MISMATCH');
519
- if (right.decimals > MAX_ORACLE_THRESHOLD_DECIMALS) {
520
- throw new OracleError('ORACLE_THRESHOLD_DECIMALS_OUT_OF_RANGE');
521
- }
522
- // Scale BOTH sides up to the wider basis; dividing the threshold down
523
- // truncates and moves the permit boundary.
524
- const decimals = BigInt(right.decimals);
525
- const normalised = BigInt(NORMALISED_DECIMALS);
526
- const common = decimals > normalised ? decimals : normalised;
527
- const priceScaled = BigInt(entry.price) * 10n ** (common - normalised);
528
- const literalScaled = BigInt(right.value) * 10n ** (common - decimals);
529
- // A violated oracle threshold is a stateful bound (Rust dsl.rs:276 returns
530
- // `DenyReason::StatefulBound`, deny code #104). The TS evaluator must mirror
531
- // the same reason so a future TS/Rust reason divergence fails CI; the
532
- // cross-layer harness in this file now asserts reason codes match.
533
- return bigintCmp(op, String(priceScaled), String(literalScaled))
534
- ? { permit: true }
535
- : { permit: false, reason: 'STATEFUL_BOUND' };
536
- }
537
- /** BigInt compare helper. `eq` is also supported by callers (selector-vs-literal
538
- * equal checks), but the dedicated arg path uses it via `BigInt(actual) === BigInt(right)`. */
264
+ /** BigInt compare helper. */
539
265
  function bigintCmp(op, aStr, bStr) {
540
266
  const a = BigInt(aStr);
541
267
  const b = BigInt(bStr);
542
268
  switch (op) {
543
269
  case 'eq':
544
270
  return a === b;
545
- case 'lt':
546
- return a < b;
547
271
  case 'lte':
548
272
  return a <= b;
549
- case 'gt':
550
- return a > b;
551
- case 'gte':
552
- return a >= b;
553
273
  }
554
274
  }
@@ -1,10 +1,5 @@
1
1
  export { isStellarAddress } from './address.ts';
2
2
  export { type ComposeOptions, type ComposeResult, composeFromRecording, } from './compose-from-recording.ts';
3
- export { type DenyCase, type GeneratedCases, generateCases, } from './deny-cases.ts';
4
- export { type EvalContext, type EvalResult, evaluate } from './evaluate.ts';
5
- export { type HarnessFailure, type HarnessResult, runHarness, } from './harness.ts';
6
3
  export { type IntentFacts, lower } from './lower.ts';
7
- export { minimize } from './minimize.ts';
8
4
  export { type DecideScopeOptions, decideScope, type ScopeDecision, scopeToContextRuleType, } from './scope.ts';
9
- export { synthesizeFromMandate } from './synthesize-from-mandate.ts';
10
5
  export { type __TestInterpreterAdapterOptions, type SynthesizeFromRecordingOptions, synthesizeFromRecording, } from './synthesize-from-recording.ts';
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // src/synth/index.ts - re-export the synthesizer front-ends.
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.synthesizeFromRecording = exports.synthesizeFromMandate = exports.scopeToContextRuleType = exports.decideScope = exports.minimize = exports.lower = exports.runHarness = exports.evaluate = exports.generateCases = exports.composeFromRecording = exports.isStellarAddress = void 0;
4
+ exports.synthesizeFromRecording = exports.scopeToContextRuleType = exports.decideScope = exports.lower = exports.composeFromRecording = exports.isStellarAddress = void 0;
5
5
  // Exported because `swapRecipientAllowlist` is part of the public synthesis
6
6
  // input: a caller assembling one needs the same validator the schema applies,
7
7
  // and re-deriving it elsewhere means a second address check that can drift.
@@ -9,20 +9,10 @@ var address_ts_1 = require("./address.js");
9
9
  Object.defineProperty(exports, "isStellarAddress", { enumerable: true, get: function () { return address_ts_1.isStellarAddress; } });
10
10
  var compose_from_recording_ts_1 = require("./compose-from-recording.js");
11
11
  Object.defineProperty(exports, "composeFromRecording", { enumerable: true, get: function () { return compose_from_recording_ts_1.composeFromRecording; } });
12
- var deny_cases_ts_1 = require("./deny-cases.js");
13
- Object.defineProperty(exports, "generateCases", { enumerable: true, get: function () { return deny_cases_ts_1.generateCases; } });
14
- var evaluate_ts_1 = require("./evaluate.js");
15
- Object.defineProperty(exports, "evaluate", { enumerable: true, get: function () { return evaluate_ts_1.evaluate; } });
16
- var harness_ts_1 = require("./harness.js");
17
- Object.defineProperty(exports, "runHarness", { enumerable: true, get: function () { return harness_ts_1.runHarness; } });
18
12
  var lower_ts_1 = require("./lower.js");
19
13
  Object.defineProperty(exports, "lower", { enumerable: true, get: function () { return lower_ts_1.lower; } });
20
- var minimize_ts_1 = require("./minimize.js");
21
- Object.defineProperty(exports, "minimize", { enumerable: true, get: function () { return minimize_ts_1.minimize; } });
22
14
  var scope_ts_1 = require("./scope.js");
23
15
  Object.defineProperty(exports, "decideScope", { enumerable: true, get: function () { return scope_ts_1.decideScope; } });
24
16
  Object.defineProperty(exports, "scopeToContextRuleType", { enumerable: true, get: function () { return scope_ts_1.scopeToContextRuleType; } });
25
- var synthesize_from_mandate_ts_1 = require("./synthesize-from-mandate.js");
26
- Object.defineProperty(exports, "synthesizeFromMandate", { enumerable: true, get: function () { return synthesize_from_mandate_ts_1.synthesizeFromMandate; } });
27
17
  var synthesize_from_recording_ts_1 = require("./synthesize-from-recording.js");
28
18
  Object.defineProperty(exports, "synthesizeFromRecording", { enumerable: true, get: function () { return synthesize_from_recording_ts_1.synthesizeFromRecording; } });
@@ -1,15 +1,8 @@
1
- import type { PredicateNode, RecordedTransaction } from '../types.ts';
1
+ import type { RecordedTransaction } from '../types.ts';
2
2
  import type { EvalContext } from './evaluate.ts';
3
3
  export interface PermitContextResponses {
4
4
  windowSeconds: number;
5
- invocationLimit?: number;
6
5
  limitAmount?: string;
7
6
  validUntilLedger: number;
8
- oraclePriceBound?: Array<{
9
- asset: string;
10
- operator: string;
11
- value: string;
12
- decimals: number;
13
- }>;
14
7
  }
15
- export declare function buildPermitContext(tx: RecordedTransaction, responses: PermitContextResponses, predicate: PredicateNode): EvalContext;
8
+ export declare function buildPermitContext(tx: RecordedTransaction, responses: PermitContextResponses): EvalContext;