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