@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
@@ -7,23 +7,13 @@
7
7
  // summary being REPRODUCIBLE + TESTABLE + NON-HALLUCINABLE, so this module is
8
8
  // pure: same inputs -> byte-identical output, no clock, no randomness, no I/O.
9
9
  //
10
- // The builder walks two inputs and emits ONE constraint string per leaf or
11
- // primitive, in a fixed deterministic order:
12
- //
13
- // 1. The OZ built-in `PolicyRef`s. Each `spending_limit` primitive becomes a
14
- // `spending_limit(token, limitAmount, windowSecs)` line; other OZ
15
- // primitives (threshold) are skipped - the review card does not quote
16
- // them (they are signer-config concerns, not transactional bounds).
17
- //
18
- // 2. The interpreter `PredicateNode`. One string per constraint leaf,
19
- // rendered by enclosing-comparison kind. Templates (Task 7b):
20
- // - invocation_count_in_window < N -> At most N calls per <window> seconds
21
- // - call_arg[i] in [list] -> Recipient/arg must be one of [list]
22
- // - eq(call_arg[i], literal_vec[...]) -> Path must be exactly [list]
23
- // - oracle_price(asset) OP price -> Only when oracle_price(asset) OP price
24
- // - call_fn == x -> Function must be x
25
- // - call_contract == c -> Contract must be c
26
- // - amount <= v -> Amount <= v
10
+ // The builder walks the interpreter `PredicateNode` and emits ONE constraint
11
+ // string per leaf, in a fixed deterministic order. Templates (Task 7b):
12
+ // - call_arg[i] in [list] -> Recipient/arg must be one of [list]
13
+ // - eq(call_arg[i], literal_vec[...]) -> Path must be exactly [list]
14
+ // - call_fn == x -> Function must be x
15
+ // - call_contract == c -> Contract must be c
16
+ // - amount <= v -> Amount <= v
27
17
  //
28
18
  // The content hash is a stable sha256 hex of a canonical JSON of
29
19
  // { ruleName, plainEnglish, constraints, expiry, backend } - identical
@@ -33,15 +23,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
33
23
  exports.buildReviewCardSummary = buildReviewCardSummary;
34
24
  exports.describePredicate = describePredicate;
35
25
  const node_crypto_1 = require("node:crypto");
26
+ const render_leaf_ts_1 = require("./render-leaf.js");
36
27
  /** Build a deterministic review-card summary from a policy + context rule +
37
28
  * simulation result. Pure: same inputs -> byte-identical output. */
38
- function buildReviewCardSummary(predicate, policyRefs, contextRule, simulation) {
29
+ function buildReviewCardSummary(predicate, contextRule,
30
+ /** Evaluator identity folded into the content hash. */
31
+ backend) {
39
32
  const constraints = [];
40
- for (const ref of policyRefs) {
41
- const line = renderOzPrimitive(ref);
42
- if (line !== null)
43
- constraints.push(line);
44
- }
45
33
  if (predicate !== null) {
46
34
  walkPredicate(predicate, (node) => {
47
35
  const line = renderConstraint(node);
@@ -59,7 +47,6 @@ function buildReviewCardSummary(predicate, policyRefs, contextRule, simulation)
59
47
  const signerNote = contextRule.signers.length >= 2
60
48
  ? 'any ONE signer may authorise a permitted op under this rule (OZ any-of-N semantic)'
61
49
  : null;
62
- const backend = simulation.backend;
63
50
  const contentHash = computeContentHash({
64
51
  ruleName,
65
52
  plainEnglish,
@@ -70,25 +57,6 @@ function buildReviewCardSummary(predicate, policyRefs, contextRule, simulation)
70
57
  });
71
58
  return { ruleName, plainEnglish, constraints, expiry, signerNote, backend, contentHash };
72
59
  }
73
- /** Render the OZ built-in primitive summary line. Only `spending_limit` is
74
- * quoted by the review card (it is the only primitive that defines a
75
- * transactional bound). Other primitives (threshold) are signer-config
76
- * concerns handled by the OZ adapter's own `uncovered` machinery.
77
- * Spending_limit takes `period_ledgers` on-chain (~5s/ledger); the card
78
- * states the window in seconds so the user reads it consistently with the
79
- * interpreter templates. */
80
- function renderOzPrimitive(ref) {
81
- if (ref.kind !== 'oz_builtin')
82
- return null;
83
- const primitive = ref.primitive;
84
- if (primitive.primitive !== 'spending_limit')
85
- return null;
86
- const params = primitive.params;
87
- const limit = params.spending_limit ?? '0';
88
- const periodLedgers = params.period_ledgers ?? 0;
89
- const windowSecs = periodLedgers * 5;
90
- return `spending_limit(${limit}, ${windowSecs})`;
91
- }
92
60
  /** Walk every comparison / membership node of the predicate and invoke
93
61
  * `visit` on each. The walk is depth-first, left-to-right, so the
94
62
  * constraint list is stable across runs. Pure boolean nodes contribute no
@@ -113,21 +81,14 @@ function describePredicate(predicate) {
113
81
  function walkPredicate(node, visit) {
114
82
  switch (node.op) {
115
83
  case 'and':
116
- case 'or':
117
84
  for (const child of node.children)
118
85
  walkPredicate(child, visit);
119
86
  return;
120
- case 'not':
121
- walkPredicate(node.child, visit);
122
- return;
123
87
  case 'in':
124
88
  visit(node);
125
89
  return;
126
90
  case 'eq':
127
- case 'lt':
128
91
  case 'lte':
129
- case 'gt':
130
- case 'gte':
131
92
  visit(node);
132
93
  return;
133
94
  }
@@ -135,18 +96,13 @@ function walkPredicate(node, visit) {
135
96
  /** Render ONE constraint sentence for ONE interpreter predicate node. The
136
97
  * shape of the output is pinned by Task 7b so the test suite can assert
137
98
  * byte-for-byte equality. Returns `null` when the node is a structural
138
- * boolean (`and` / `or` / `not`) - those are not constraint leaves. */
99
+ * boolean (`and`) - that is not a constraint leaf. */
139
100
  function renderConstraint(node) {
140
101
  switch (node.op) {
141
102
  case 'and':
142
- case 'or':
143
- case 'not':
144
103
  return null;
145
104
  case 'eq':
146
- case 'lt':
147
105
  case 'lte':
148
- case 'gt':
149
- case 'gte':
150
106
  return renderComparison(node);
151
107
  case 'in':
152
108
  return renderMembership(node);
@@ -165,7 +121,7 @@ function renderComparison(node) {
165
121
  }
166
122
  // eq(call_arg[i], literal_vec) -> Path must be exactly [list]
167
123
  if (left.kind === 'call_arg' && node.op === 'eq' && right.kind === 'literal_vec') {
168
- return `Path must be exactly [${right.elements.map(renderVecElement).join(', ')}]`;
124
+ return `Path must be exactly [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`;
169
125
  }
170
126
  // eq(call_arg_len[i], literal_u32) -> Length of arg[i] is K
171
127
  // Binds the OUTER vec length so a caller cannot append an element to
@@ -176,73 +132,41 @@ function renderComparison(node) {
176
132
  }
177
133
  // eq/lte(call_arg_field[i, el, field], <literal>) -> arg[i] element[el].field = <value>
178
134
  // The structured-argument bind pins a scalar field inside a vec element.
179
- // For lt/gt/lte/gte the line reads "OP <value>" so the comparison kind
180
- // is visible; eq reads "= <value>".
135
+ // For lte the line reads "OP <value>" so the comparison kind is visible;
136
+ // eq reads "= <value>".
181
137
  if (left.kind === 'call_arg_field') {
182
138
  const head = `arg[${left.index}] element[${left.element}].${left.field}`;
183
- const sep = node.op === 'eq' ? '=' : comparisonOpText(node.op);
139
+ const sep = node.op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(node.op);
184
140
  if (right.kind === 'literal_address')
185
141
  return `${head} ${sep} ${right.value}`;
186
142
  if (right.kind === 'literal_symbol')
187
143
  return `${head} ${sep} ${right.value}`;
188
- if (right.kind === 'literal_bytes')
189
- return `${head} ${sep} ${right.value}`;
190
- if (right.kind === 'literal_u64')
191
- return `${head} ${sep} ${right.value}`;
192
144
  if (right.kind === 'literal_i128')
193
145
  return `${head} ${sep} ${right.value}`;
194
146
  if (right.kind === 'literal_u32')
195
147
  return `${head} ${sep} ${right.value}`;
196
148
  if (right.kind === 'literal_vec') {
197
- return `${head} ${sep} [${right.elements.map(renderVecElement).join(', ')}]`;
149
+ return `${head} ${sep} [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`;
198
150
  }
199
151
  }
200
- // The bound is compared against the calls ALREADY made in the window, so
201
- // `< N` permits N of them and `<= N` permits one more. Report how many
202
- // calls the rule allows rather than restating the comparison.
203
- if (left.kind === 'invocation_count_in_window' && right.kind === 'literal_u32') {
204
- const allowed = node.op === 'lt' ? right.value : right.value + 1;
205
- return `At most ${allowed} calls per ${left.windowSecs} seconds`;
206
- }
207
- // OP(call_arg[i], <scalar literal>) -> arg[i] OP <value>
208
- //
209
- // The per-call cap. It has to be here because the `amount` template below
210
- // is unreachable for an interpreter policy: `amount` is deliberately not in
211
- // the contract's grammar (dsl.rs - the interpreter sees one authorized
212
- // call, not the transaction's token movements), so a predicate using it is
213
- // refused at install. A bound on the call's own amount ARGUMENT is how a
214
- // cap is actually written, and it was rendering nothing at all - the card
215
- // silently understated the policy.
152
+ // The per-call cap: OP(call_arg[i], <scalar literal>) -> arg[i] OP <value>.
153
+ // The interpreter is passed one authorized call, not the transaction's token
154
+ // movements, so a bound on the call's own amount ARGUMENT is how a cap is
155
+ // written.
216
156
  //
217
157
  // Placed after the literal_vec case above so an exact-sequence `eq` still
218
158
  // reads as a path rather than as a comparison.
219
159
  if (left.kind === 'call_arg') {
220
160
  const head = `arg[${left.index}]`;
221
- const sep = node.op === 'eq' ? '=' : comparisonOpText(node.op);
161
+ const sep = node.op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(node.op);
222
162
  if (right.kind === 'literal_i128')
223
163
  return `${head} ${sep} ${right.value}`;
224
- if (right.kind === 'literal_u64')
225
- return `${head} ${sep} ${right.value}`;
226
164
  if (right.kind === 'literal_u32')
227
165
  return `${head} ${sep} ${right.value}`;
228
166
  if (right.kind === 'literal_address')
229
167
  return `${head} ${sep} ${right.value}`;
230
168
  if (right.kind === 'literal_symbol')
231
169
  return `${head} ${sep} ${right.value}`;
232
- if (right.kind === 'literal_bytes')
233
- return `${head} ${sep} ${right.value}`;
234
- // call_arg_scaled is the slippage floor and reads as itself.
235
- if (right.kind === 'call_arg_scaled') {
236
- return `${head} >= arg[${right.index}] * ${right.num}/${right.den}`;
237
- }
238
- }
239
- // amount <= v -> Amount <= v
240
- if (left.kind === 'amount' && right.kind === 'literal_i128') {
241
- return `Amount <= ${right.value}`;
242
- }
243
- // oracle_price(asset) OP price -> Only when oracle_price(asset) OP price
244
- if (left.kind === 'oracle_price' && right.kind === 'oracle_threshold') {
245
- return `Only when oracle_price(${left.asset}) ${comparisonOpText(node.op)} ${right.value} (${right.decimals} dp)`;
246
170
  }
247
171
  // Any other comparison shape is a structural fail-closed: do not surface
248
172
  // a misleading line. Cross-check still requires every leaf produce a
@@ -254,79 +178,11 @@ function renderComparison(node) {
254
178
  function renderMembership(node) {
255
179
  // call_arg[i] in [list] -> Recipient/arg must be one of [list]
256
180
  if (node.needle.kind === 'call_arg') {
257
- const list = node.haystack.map(renderHaystackElement).join(', ');
181
+ const list = node.haystack.map(render_leaf_ts_1.renderHaystackElement).join(', ');
258
182
  return `Recipient/arg must be one of [${list}]`;
259
183
  }
260
184
  return null;
261
185
  }
262
- function renderVecElement(leaf) {
263
- switch (leaf.kind) {
264
- case 'literal_address':
265
- return leaf.value;
266
- case 'literal_i128':
267
- return leaf.value;
268
- case 'literal_symbol':
269
- return leaf.value;
270
- case 'literal_u32':
271
- return String(leaf.value);
272
- case 'literal_u64':
273
- return leaf.value;
274
- case 'literal_bytes':
275
- return leaf.value;
276
- case 'literal_vec':
277
- return `[${leaf.elements.map(renderVecElement).join(', ')}]`;
278
- case 'call_contract':
279
- case 'call_fn':
280
- case 'call_arg':
281
- case 'call_arg_len':
282
- case 'call_arg_field':
283
- case 'call_arg_scaled':
284
- case 'amount':
285
- case 'window_spent':
286
- case 'now':
287
- case 'valid_until':
288
- case 'invocation_count_in_window':
289
- case 'oracle_price':
290
- return `<${leaf.kind}>`;
291
- // Show the declared basis, not just the digits. A threshold on the wrong
292
- // basis is the one policy error the contract cannot detect, so the review
293
- // card is where a human has to be able to see it.
294
- case 'oracle_threshold':
295
- return `${leaf.value} (${leaf.decimals} dp)`;
296
- }
297
- }
298
- function renderHaystackElement(leaf) {
299
- if (leaf.kind === 'literal_address')
300
- return leaf.value;
301
- if (leaf.kind === 'literal_i128')
302
- return leaf.value;
303
- if (leaf.kind === 'literal_symbol')
304
- return leaf.value;
305
- if (leaf.kind === 'literal_u32')
306
- return String(leaf.value);
307
- if (leaf.kind === 'literal_u64')
308
- return leaf.value;
309
- if (leaf.kind === 'literal_bytes')
310
- return leaf.value;
311
- if (leaf.kind === 'literal_vec') {
312
- return `[${leaf.elements.map(renderHaystackElement).join(', ')}]`;
313
- }
314
- return `<${leaf.kind}>`;
315
- }
316
- function comparisonOpText(op) {
317
- switch (op) {
318
- case 'lt':
319
- return '<';
320
- case 'lte':
321
- return '<=';
322
- case 'gt':
323
- return '>';
324
- case 'gte':
325
- return '>=';
326
- case 'eq':
327
- return '==';
328
- }
329
- }
330
186
  /** Render the plain-English one-liner. Format: `<ruleName>: <constraints>`,
331
187
  * joined by `; ` so the user reads one sentence per constraint. */
332
188
  function renderPlainEnglish(ruleName, constraints) {
@@ -15,6 +15,7 @@
15
15
  // guard.
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.summaryCrossCheck = summaryCrossCheck;
18
+ const render_leaf_ts_1 = require("./render-leaf.js");
18
19
  /** Assert every leaf in the predicate appears as a constraint string in
19
20
  * `summary.constraints`. Returns the missing templates (without the leaf
20
21
  * values filled in) when any leaf was dropped. */
@@ -38,18 +39,11 @@ function summaryCrossCheck(predicate, summary) {
38
39
  function collect(node, out) {
39
40
  switch (node.op) {
40
41
  case 'and':
41
- case 'or':
42
42
  for (const child of node.children)
43
43
  collect(child, out);
44
44
  return;
45
- case 'not':
46
- collect(node.child, out);
47
- return;
48
45
  case 'eq':
49
- case 'lt':
50
46
  case 'lte':
51
- case 'gt':
52
- case 'gte':
53
47
  pushComparison(node.left, node.right, node.op, out);
54
48
  return;
55
49
  case 'in':
@@ -67,7 +61,7 @@ function pushComparison(left, right, op, out) {
67
61
  return;
68
62
  }
69
63
  if (left.kind === 'call_arg' && op === 'eq' && right.kind === 'literal_vec') {
70
- out.push(`Path must be exactly [${right.elements.map(renderVecElement).join(', ')}]`);
64
+ out.push(`Path must be exactly [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`);
71
65
  return;
72
66
  }
73
67
  if (left.kind === 'call_arg_len' && op === 'eq' && right.kind === 'literal_u32') {
@@ -78,7 +72,7 @@ function pushComparison(left, right, op, out) {
78
72
  // the cross-check can assert a faithful summary.
79
73
  if (left.kind === 'call_arg_field') {
80
74
  const head = `arg[${left.index}] element[${left.element}].${left.field}`;
81
- const sep = op === 'eq' ? '=' : comparisonOpText(op);
75
+ const sep = op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(op);
82
76
  if (right.kind === 'literal_address') {
83
77
  out.push(`${head} ${sep} ${right.value}`);
84
78
  return;
@@ -87,14 +81,6 @@ function pushComparison(left, right, op, out) {
87
81
  out.push(`${head} ${sep} ${right.value}`);
88
82
  return;
89
83
  }
90
- if (right.kind === 'literal_bytes') {
91
- out.push(`${head} ${sep} ${right.value}`);
92
- return;
93
- }
94
- if (right.kind === 'literal_u64') {
95
- out.push(`${head} ${sep} ${right.value}`);
96
- return;
97
- }
98
84
  if (right.kind === 'literal_i128') {
99
85
  out.push(`${head} ${sep} ${right.value}`);
100
86
  return;
@@ -104,117 +90,28 @@ function pushComparison(left, right, op, out) {
104
90
  return;
105
91
  }
106
92
  if (right.kind === 'literal_vec') {
107
- out.push(`${head} ${sep} [${right.elements.map(renderVecElement).join(', ')}]`);
93
+ out.push(`${head} ${sep} [${right.elements.map(render_leaf_ts_1.renderVecElement).join(', ')}]`);
108
94
  return;
109
95
  }
110
96
  }
111
- // Mirrors the builder's wording byte-for-byte: how many calls the rule
112
- // permits, since the bound counts the calls already made.
113
- if (left.kind === 'invocation_count_in_window' && right.kind === 'literal_u32') {
114
- const allowed = op === 'lt' ? right.value : right.value + 1;
115
- out.push(`At most ${allowed} calls per ${left.windowSecs} seconds`);
116
- return;
117
- }
118
97
  // Mirrors the builder's per-call cap line. Both sides must render the same
119
98
  // string: cross-check compares the summary against the predicate, so a
120
99
  // shape only ONE of them renders would read as a dropped constraint.
121
100
  if (left.kind === 'call_arg') {
122
101
  const head = `arg[${left.index}]`;
123
- const sep = op === 'eq' ? '=' : comparisonOpText(op);
102
+ const sep = op === 'eq' ? '=' : (0, render_leaf_ts_1.comparisonOpText)(op);
124
103
  if (right.kind === 'literal_i128' ||
125
- right.kind === 'literal_u64' ||
126
104
  right.kind === 'literal_u32' ||
127
105
  right.kind === 'literal_address' ||
128
- right.kind === 'literal_symbol' ||
129
- right.kind === 'literal_bytes') {
106
+ right.kind === 'literal_symbol') {
130
107
  out.push(`${head} ${sep} ${right.value}`);
131
108
  return;
132
109
  }
133
- if (right.kind === 'call_arg_scaled') {
134
- out.push(`${head} >= arg[${right.index}] * ${right.num}/${right.den}`);
135
- return;
136
- }
137
- }
138
- if (left.kind === 'amount' && right.kind === 'literal_i128') {
139
- out.push(`Amount <= ${right.value}`);
140
- return;
141
- }
142
- if (left.kind === 'oracle_price' && right.kind === 'oracle_threshold') {
143
- out.push(`Only when oracle_price(${left.asset}) ${comparisonOpText(op)} ${right.value} (${right.decimals} dp)`);
144
- return;
145
110
  }
146
111
  }
147
112
  function pushMembership(needle, haystack, out) {
148
113
  if (needle.kind !== 'call_arg')
149
114
  return;
150
- const list = haystack.map(renderHaystackElement).join(', ');
115
+ const list = haystack.map(render_leaf_ts_1.renderHaystackElement).join(', ');
151
116
  out.push(`Recipient/arg must be one of [${list}]`);
152
117
  }
153
- function renderVecElement(leaf) {
154
- switch (leaf.kind) {
155
- case 'literal_address':
156
- return leaf.value;
157
- case 'literal_i128':
158
- return leaf.value;
159
- case 'literal_symbol':
160
- return leaf.value;
161
- case 'literal_u32':
162
- return String(leaf.value);
163
- case 'literal_u64':
164
- return leaf.value;
165
- case 'literal_bytes':
166
- return leaf.value;
167
- case 'literal_vec':
168
- return `[${leaf.elements.map(renderVecElement).join(', ')}]`;
169
- case 'call_contract':
170
- case 'call_fn':
171
- case 'call_arg':
172
- case 'call_arg_len':
173
- case 'call_arg_field':
174
- case 'call_arg_scaled':
175
- case 'amount':
176
- case 'window_spent':
177
- case 'now':
178
- case 'valid_until':
179
- case 'invocation_count_in_window':
180
- case 'oracle_price':
181
- return `<${leaf.kind}>`;
182
- // Show the declared basis, not just the digits. A threshold on the wrong
183
- // basis is the one policy error the contract cannot detect, so the review
184
- // card is where a human has to be able to see it.
185
- case 'oracle_threshold':
186
- return `${leaf.value} (${leaf.decimals} dp)`;
187
- }
188
- }
189
- function renderHaystackElement(leaf) {
190
- if (leaf.kind === 'literal_address')
191
- return leaf.value;
192
- if (leaf.kind === 'literal_i128')
193
- return leaf.value;
194
- if (leaf.kind === 'literal_symbol')
195
- return leaf.value;
196
- if (leaf.kind === 'literal_u32')
197
- return String(leaf.value);
198
- if (leaf.kind === 'literal_u64')
199
- return leaf.value;
200
- if (leaf.kind === 'literal_bytes')
201
- return leaf.value;
202
- if (leaf.kind === 'literal_vec') {
203
- return `[${leaf.elements.map(renderHaystackElement).join(', ')}]`;
204
- }
205
- return `<${leaf.kind}>`;
206
- }
207
- function comparisonOpText(op) {
208
- switch (op) {
209
- case 'lt':
210
- return '<';
211
- case 'lte':
212
- return '<=';
213
- case 'gt':
214
- return '>';
215
- case 'gte':
216
- return '>=';
217
- case 'eq':
218
- return '==';
219
- }
220
- }
@@ -0,0 +1,4 @@
1
+ import type { PredicateLeaf } from '../types.ts';
2
+ export declare function renderVecElement(leaf: PredicateLeaf): string;
3
+ export declare function renderHaystackElement(leaf: PredicateLeaf): string;
4
+ export declare function comparisonOpText(op: 'eq' | 'lte'): string;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ // Leaf and operator rendering shared by the review-card builder and the
3
+ // summary cross-check. Both turn the SAME predicate into human-readable text -
4
+ // the cross-check exists to confirm the builder's summary matches the predicate
5
+ // - so rendering a leaf differently in the two would make the check compare a
6
+ // string against itself under a different spelling and pass on a real mismatch.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.renderVecElement = renderVecElement;
9
+ exports.renderHaystackElement = renderHaystackElement;
10
+ exports.comparisonOpText = comparisonOpText;
11
+ function renderVecElement(leaf) {
12
+ switch (leaf.kind) {
13
+ case 'literal_address':
14
+ return leaf.value;
15
+ case 'literal_i128':
16
+ return leaf.value;
17
+ case 'literal_symbol':
18
+ return leaf.value;
19
+ case 'literal_u32':
20
+ return String(leaf.value);
21
+ case 'literal_vec':
22
+ return `[${leaf.elements.map(renderVecElement).join(', ')}]`;
23
+ case 'call_contract':
24
+ case 'call_fn':
25
+ case 'call_arg':
26
+ case 'call_arg_len':
27
+ case 'call_arg_field':
28
+ return `<${leaf.kind}>`;
29
+ }
30
+ }
31
+ function renderHaystackElement(leaf) {
32
+ if (leaf.kind === 'literal_address')
33
+ return leaf.value;
34
+ if (leaf.kind === 'literal_i128')
35
+ return leaf.value;
36
+ if (leaf.kind === 'literal_symbol')
37
+ return leaf.value;
38
+ if (leaf.kind === 'literal_u32')
39
+ return String(leaf.value);
40
+ if (leaf.kind === 'literal_vec') {
41
+ return `[${leaf.elements.map(renderHaystackElement).join(', ')}]`;
42
+ }
43
+ return `<${leaf.kind}>`;
44
+ }
45
+ function comparisonOpText(op) {
46
+ switch (op) {
47
+ case 'lte':
48
+ return '<=';
49
+ case 'eq':
50
+ return '==';
51
+ }
52
+ }