@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,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;