@crediolabs/policy-synth 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/README.md +4 -4
  2. package/dist/adapters/interpreter/adapter.d.ts +19 -25
  3. package/dist/adapters/interpreter/adapter.js +54 -487
  4. package/dist/errors.d.ts +1 -1
  5. package/dist/index.d.ts +1 -4
  6. package/dist/index.js +1 -4
  7. package/dist/install/build-add-context-rule.d.ts +3 -19
  8. package/dist/install/build-add-context-rule.js +3 -98
  9. package/dist/install/build-install-policy.d.ts +4 -53
  10. package/dist/install/build-install-policy.js +44 -123
  11. package/dist/install/index.d.ts +0 -2
  12. package/dist/install/index.js +0 -7
  13. package/dist/predicate/decode.d.ts +1 -1
  14. package/dist/predicate/decode.js +2 -70
  15. package/dist/predicate/encode.js +45 -229
  16. package/dist/predicate/from-json.js +1 -42
  17. package/dist/record/decode.js +3 -8
  18. package/dist/record/freshness.d.ts +17 -11
  19. package/dist/record/freshness.js +28 -18
  20. package/dist/review-card/builder.d.ts +4 -3
  21. package/dist/review-card/builder.js +18 -162
  22. package/dist/review-card/cross-check.js +2 -105
  23. package/dist/review-card/render-leaf.d.ts +4 -0
  24. package/dist/review-card/render-leaf.js +47 -0
  25. package/dist/run/index.d.ts +36 -139
  26. package/dist/run/index.js +125 -437
  27. package/dist/run/schemas.d.ts +168 -1929
  28. package/dist/run/schemas.js +40 -269
  29. package/dist/{synth → simulate}/deny-cases.d.ts +4 -8
  30. package/dist/simulate/deny-cases.js +282 -0
  31. package/dist/simulate/evaluate.d.ts +18 -0
  32. package/dist/simulate/evaluate.js +271 -0
  33. package/dist/simulate/index.d.ts +4 -0
  34. package/dist/simulate/index.js +8 -0
  35. package/dist/synth/compose-from-recording.d.ts +24 -51
  36. package/dist/synth/compose-from-recording.js +120 -282
  37. package/dist/synth/index.d.ts +0 -5
  38. package/dist/synth/index.js +0 -5
  39. package/dist/synth/synthesize-from-recording.d.ts +1 -29
  40. package/dist/synth/synthesize-from-recording.js +41 -356
  41. package/dist/types.d.ts +8 -60
  42. package/dist/types.js +5 -2
  43. package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
  44. package/dist-cjs/adapters/interpreter/adapter.js +56 -489
  45. package/dist-cjs/errors.d.ts +1 -1
  46. package/dist-cjs/index.d.ts +1 -4
  47. package/dist-cjs/index.js +1 -4
  48. package/dist-cjs/install/build-add-context-rule.d.ts +3 -19
  49. package/dist-cjs/install/build-add-context-rule.js +1 -97
  50. package/dist-cjs/install/build-install-policy.d.ts +4 -53
  51. package/dist-cjs/install/build-install-policy.js +44 -122
  52. package/dist-cjs/install/index.d.ts +0 -2
  53. package/dist-cjs/install/index.js +2 -23
  54. package/dist-cjs/predicate/decode.d.ts +1 -1
  55. package/dist-cjs/predicate/decode.js +2 -70
  56. package/dist-cjs/predicate/encode.js +45 -229
  57. package/dist-cjs/predicate/from-json.js +1 -42
  58. package/dist-cjs/record/decode.js +3 -8
  59. package/dist-cjs/record/freshness.d.ts +17 -11
  60. package/dist-cjs/record/freshness.js +28 -18
  61. package/dist-cjs/review-card/builder.d.ts +4 -3
  62. package/dist-cjs/review-card/builder.js +23 -167
  63. package/dist-cjs/review-card/cross-check.js +7 -110
  64. package/dist-cjs/review-card/render-leaf.d.ts +4 -0
  65. package/dist-cjs/review-card/render-leaf.js +52 -0
  66. package/dist-cjs/run/index.d.ts +36 -139
  67. package/dist-cjs/run/index.js +125 -444
  68. package/dist-cjs/run/schemas.d.ts +168 -1929
  69. package/dist-cjs/run/schemas.js +41 -270
  70. package/dist-cjs/{synth → simulate}/deny-cases.d.ts +4 -8
  71. package/dist-cjs/simulate/deny-cases.js +286 -0
  72. package/dist-cjs/simulate/evaluate.d.ts +18 -0
  73. package/dist-cjs/simulate/evaluate.js +274 -0
  74. package/dist-cjs/simulate/index.d.ts +4 -0
  75. package/dist-cjs/simulate/index.js +13 -0
  76. package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
  77. package/dist-cjs/synth/compose-from-recording.js +120 -282
  78. package/dist-cjs/synth/index.d.ts +0 -5
  79. package/dist-cjs/synth/index.js +1 -11
  80. package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
  81. package/dist-cjs/synth/synthesize-from-recording.js +39 -354
  82. package/dist-cjs/types.d.ts +8 -60
  83. package/dist-cjs/types.js +6 -3
  84. package/package.json +5 -5
  85. package/src/adapters/interpreter/adapter.ts +76 -572
  86. package/src/errors.ts +0 -19
  87. package/src/index.ts +1 -4
  88. package/src/install/build-add-context-rule.ts +5 -139
  89. package/src/install/build-install-policy.ts +93 -214
  90. package/src/install/index.ts +0 -34
  91. package/src/predicate/decode.ts +2 -70
  92. package/src/predicate/encode.ts +49 -261
  93. package/src/predicate/from-json.ts +1 -43
  94. package/src/record/decode.ts +3 -8
  95. package/src/record/freshness.ts +29 -18
  96. package/src/review-card/builder.ts +19 -168
  97. package/src/review-card/cross-check.ts +3 -105
  98. package/src/review-card/render-leaf.ts +48 -0
  99. package/src/run/index.ts +144 -572
  100. package/src/run/schemas.ts +42 -291
  101. package/src/simulate/deny-cases.ts +334 -0
  102. package/src/simulate/evaluate.ts +284 -0
  103. package/src/simulate/index.ts +11 -0
  104. package/src/synth/compose-from-recording.ts +148 -347
  105. package/src/synth/index.ts +0 -13
  106. package/src/synth/synthesize-from-recording.ts +43 -456
  107. package/src/types.ts +13 -49
  108. package/dist/adapters/interpreter/index.d.ts +0 -1
  109. package/dist/adapters/interpreter/index.js +0 -2
  110. package/dist/adapters/oz/adapter.d.ts +0 -20
  111. package/dist/adapters/oz/adapter.js +0 -295
  112. package/dist/adapters/oz/index.d.ts +0 -1
  113. package/dist/adapters/oz/index.js +0 -2
  114. package/dist/codegen/compile-gate.d.ts +0 -33
  115. package/dist/codegen/compile-gate.js +0 -124
  116. package/dist/codegen/index.d.ts +0 -2
  117. package/dist/codegen/index.js +0 -8
  118. package/dist/codegen/template.d.ts +0 -18
  119. package/dist/codegen/template.js +0 -148
  120. package/dist/install/authority-overlap.d.ts +0 -134
  121. package/dist/install/authority-overlap.js +0 -0
  122. package/dist/install/build-install-predicate.d.ts +0 -96
  123. package/dist/install/build-install-predicate.js +0 -444
  124. package/dist/install/build-merge-policy.d.ts +0 -70
  125. package/dist/install/build-merge-policy.js +0 -130
  126. package/dist/install/plan-merge-policy.d.ts +0 -49
  127. package/dist/install/plan-merge-policy.js +0 -86
  128. package/dist/install/read-account-rules.d.ts +0 -100
  129. package/dist/install/read-account-rules.js +0 -283
  130. package/dist/ir/index.d.ts +0 -1
  131. package/dist/ir/index.js +0 -2
  132. package/dist/ir/types.d.ts +0 -140
  133. package/dist/ir/types.js +0 -11
  134. package/dist/mandate/index.d.ts +0 -2
  135. package/dist/mandate/index.js +0 -2
  136. package/dist/mandate/to-ir.d.ts +0 -3
  137. package/dist/mandate/to-ir.js +0 -60
  138. package/dist/mandate/types.d.ts +0 -20
  139. package/dist/mandate/types.js +0 -8
  140. package/dist/seams/index.d.ts +0 -1
  141. package/dist/seams/index.js +0 -2
  142. package/dist/seams/types.d.ts +0 -66
  143. package/dist/seams/types.js +0 -11
  144. package/dist/synth/deny-cases.js +0 -562
  145. package/dist/synth/evaluate.d.ts +0 -39
  146. package/dist/synth/evaluate.js +0 -551
  147. package/dist/synth/harness.d.ts +0 -28
  148. package/dist/synth/harness.js +0 -47
  149. package/dist/synth/minimize.d.ts +0 -4
  150. package/dist/synth/minimize.js +0 -38
  151. package/dist/synth/permit-context.d.ts +0 -15
  152. package/dist/synth/permit-context.js +0 -116
  153. package/dist/synth/predicate-literals.d.ts +0 -5
  154. package/dist/synth/predicate-literals.js +0 -25
  155. package/dist/synth/synthesize-from-mandate.d.ts +0 -20
  156. package/dist/synth/synthesize-from-mandate.js +0 -59
  157. package/dist/verify/envelope.d.ts +0 -15
  158. package/dist/verify/envelope.js +0 -22
  159. package/dist/verify/index.d.ts +0 -3
  160. package/dist/verify/index.js +0 -3
  161. package/dist/verify/simulate.d.ts +0 -31
  162. package/dist/verify/simulate.js +0 -258
  163. package/dist/verify/verify.d.ts +0 -21
  164. package/dist/verify/verify.js +0 -189
  165. package/dist-cjs/adapters/interpreter/index.d.ts +0 -1
  166. package/dist-cjs/adapters/interpreter/index.js +0 -8
  167. package/dist-cjs/adapters/oz/adapter.d.ts +0 -20
  168. package/dist-cjs/adapters/oz/adapter.js +0 -300
  169. package/dist-cjs/adapters/oz/index.d.ts +0 -1
  170. package/dist-cjs/adapters/oz/index.js +0 -8
  171. package/dist-cjs/codegen/compile-gate.d.ts +0 -33
  172. package/dist-cjs/codegen/compile-gate.js +0 -128
  173. package/dist-cjs/codegen/index.d.ts +0 -2
  174. package/dist-cjs/codegen/index.js +0 -14
  175. package/dist-cjs/codegen/template.d.ts +0 -18
  176. package/dist-cjs/codegen/template.js +0 -151
  177. package/dist-cjs/install/authority-overlap.d.ts +0 -134
  178. package/dist-cjs/install/authority-overlap.js +0 -0
  179. package/dist-cjs/install/build-install-predicate.d.ts +0 -96
  180. package/dist-cjs/install/build-install-predicate.js +0 -479
  181. package/dist-cjs/install/build-merge-policy.d.ts +0 -70
  182. package/dist-cjs/install/build-merge-policy.js +0 -134
  183. package/dist-cjs/install/plan-merge-policy.d.ts +0 -49
  184. package/dist-cjs/install/plan-merge-policy.js +0 -90
  185. package/dist-cjs/install/read-account-rules.d.ts +0 -100
  186. package/dist-cjs/install/read-account-rules.js +0 -296
  187. package/dist-cjs/ir/index.d.ts +0 -1
  188. package/dist-cjs/ir/index.js +0 -3
  189. package/dist-cjs/ir/types.d.ts +0 -140
  190. package/dist-cjs/ir/types.js +0 -12
  191. package/dist-cjs/mandate/index.d.ts +0 -2
  192. package/dist-cjs/mandate/index.js +0 -6
  193. package/dist-cjs/mandate/to-ir.d.ts +0 -3
  194. package/dist-cjs/mandate/to-ir.js +0 -63
  195. package/dist-cjs/mandate/types.d.ts +0 -20
  196. package/dist-cjs/mandate/types.js +0 -9
  197. package/dist-cjs/seams/index.d.ts +0 -1
  198. package/dist-cjs/seams/index.js +0 -3
  199. package/dist-cjs/seams/types.d.ts +0 -66
  200. package/dist-cjs/seams/types.js +0 -12
  201. package/dist-cjs/synth/deny-cases.js +0 -568
  202. package/dist-cjs/synth/evaluate.d.ts +0 -39
  203. package/dist-cjs/synth/evaluate.js +0 -554
  204. package/dist-cjs/synth/harness.d.ts +0 -28
  205. package/dist-cjs/synth/harness.js +0 -50
  206. package/dist-cjs/synth/minimize.d.ts +0 -4
  207. package/dist-cjs/synth/minimize.js +0 -41
  208. package/dist-cjs/synth/permit-context.d.ts +0 -15
  209. package/dist-cjs/synth/permit-context.js +0 -119
  210. package/dist-cjs/synth/predicate-literals.d.ts +0 -5
  211. package/dist-cjs/synth/predicate-literals.js +0 -28
  212. package/dist-cjs/synth/synthesize-from-mandate.d.ts +0 -20
  213. package/dist-cjs/synth/synthesize-from-mandate.js +0 -62
  214. package/dist-cjs/verify/envelope.d.ts +0 -15
  215. package/dist-cjs/verify/envelope.js +0 -23
  216. package/dist-cjs/verify/index.d.ts +0 -3
  217. package/dist-cjs/verify/index.js +0 -8
  218. package/dist-cjs/verify/simulate.d.ts +0 -31
  219. package/dist-cjs/verify/simulate.js +0 -261
  220. package/dist-cjs/verify/verify.d.ts +0 -21
  221. package/dist-cjs/verify/verify.js +0 -192
  222. package/src/adapters/interpreter/index.ts +0 -8
  223. package/src/adapters/oz/adapter.ts +0 -376
  224. package/src/adapters/oz/index.ts +0 -9
  225. package/src/codegen/compile-gate.ts +0 -167
  226. package/src/codegen/index.ts +0 -17
  227. package/src/codegen/template.ts +0 -165
  228. package/src/install/authority-overlap.ts +0 -0
  229. package/src/install/build-merge-policy.ts +0 -219
  230. package/src/install/plan-merge-policy.ts +0 -133
  231. package/src/install/read-account-rules.ts +0 -376
  232. package/src/ir/index.ts +0 -13
  233. package/src/ir/types.ts +0 -132
  234. package/src/mandate/index.ts +0 -4
  235. package/src/mandate/to-ir.ts +0 -71
  236. package/src/mandate/types.ts +0 -21
  237. package/src/seams/index.ts +0 -11
  238. package/src/seams/types.ts +0 -81
  239. package/src/synth/deny-cases.ts +0 -663
  240. package/src/synth/evaluate.ts +0 -613
  241. package/src/synth/harness.ts +0 -68
  242. package/src/synth/minimize.ts +0 -48
  243. package/src/synth/permit-context.ts +0 -136
  244. package/src/synth/predicate-literals.ts +0 -27
  245. package/src/synth/synthesize-from-mandate.ts +0 -82
  246. package/src/verify/envelope.ts +0 -28
  247. package/src/verify/index.ts +0 -5
  248. package/src/verify/simulate.ts +0 -311
  249. package/src/verify/verify.ts +0 -243
@@ -1,7 +1,5 @@
1
- import type { OzAdapterConfig } from '../adapters/oz/adapter.ts';
2
1
  import type { ToolError, ToolResponse } from '../errors.ts';
3
2
  import { type Network, type PredicateNode, type ProposedPolicy, type RecordedTransaction } from '../types.ts';
4
- import type { SimulationResult } from '../verify/envelope.ts';
5
3
  import { type ComposeUserResponses } from './compose-from-recording.ts';
6
4
  /** Per-policy interpreter adapter config the caller opts into. Absent ->
7
5
  * recording path runs in week-1 mode (OZ adapter only, warnings for the
@@ -13,13 +11,6 @@ export interface InterpreterAdapterOptions {
13
11
  smartAccountAddress: string;
14
12
  /** Per-rule install nonce (first install = 1). Defaults to 1. */
15
13
  installNonce?: number;
16
- /** Per-policy oracle overrides. Tighten-only vs the wasm defaults
17
- * (maxStalenessSeconds <= 600, maxDeviationBps <= 200); widening is
18
- * rejected at the options boundary. */
19
- oracleParams?: {
20
- maxStalenessSeconds?: number;
21
- maxDeviationBps?: number;
22
- };
23
14
  }
24
15
  /** PRIVATE test-only extension of `InterpreterAdapterOptions` for the
25
16
  * `__testPredicateNode` seam. Exported under a `__` prefix so production
@@ -43,7 +34,7 @@ export interface SynthesizeFromRecordingOptions {
43
34
  * never altered by enabling explain. Absent -> success envelope is byte-identical. */
44
35
  explain?: true;
45
36
  }
46
- export declare function synthesizeFromRecording(tx: RecordedTransaction, opts: SynthesizeFromRecordingOptions, ozConfig: OzAdapterConfig): ToolResponse<ProposedPolicy> & {
37
+ export declare function synthesizeFromRecording(tx: RecordedTransaction, opts: SynthesizeFromRecordingOptions): ToolResponse<ProposedPolicy> & {
47
38
  /** Present iff `opts.explain === true` and the synthesis succeeded. The
48
39
  * `predicateTree` is the exact in-memory `PredicateNode` (canonical
49
40
  * JSON shape) that was encoded into `proposed.policyDocuments[*].encodedPredicate`
@@ -56,28 +47,9 @@ export declare function synthesizeFromRecording(tx: RecordedTransaction, opts: S
56
47
  * builds a minimal honest SimulationResult downstream. */
57
48
  explain?: {
58
49
  predicateTree: PredicateNode | null;
59
- simulation: SimulationResult;
60
50
  };
61
51
  };
62
52
  /** ToolError-shaped error helper used by the body to surface a structured
63
53
  * failure that the envelope converts to `{ok:false, error}`. */
64
54
  declare function throwToolError(code: ToolError['code'], message: string): never;
65
55
  export { throwToolError };
66
- /** Diagnostic reported by `visitOracleLeaves` when an oracle comparison
67
- * cannot be normalised to oracle-on-left. Currently only "oracle-on-right
68
- * with a non-literal LHS" - the other branches already produce a parseable
69
- * threshold and are returned to the caller. */
70
- export interface OracleNormalisationWarning {
71
- /** The dimension name (currently always "oracle_normalisation_dropped"). */
72
- dimension: 'oracle_normalisation_dropped';
73
- /** The operator as written. */
74
- op: 'eq' | 'lt' | 'lte' | 'gt' | 'gte';
75
- /** The asset the oracle leaf is bound to. */
76
- asset: string;
77
- /** The non-literal RHS (oracle-on-left) or LHS (oracle-on-right) leaf
78
- * whose shape stopped the normalisation. Surfaced for diagnostics;
79
- * intentionally a partial view - the full leaf is the caller's job. */
80
- otherKind: string;
81
- /** Human-readable message (matches the warn-on-drop text). */
82
- message: string;
83
- }
@@ -2,33 +2,28 @@
2
2
  // src/synth/synthesize-from-recording.ts - recording-path orchestrator.
3
3
  //
4
4
  // `synthesizeFromRecording` INFERS a bounded policy from a `RecordedTransaction`
5
- // via the same `PolicyIR` + adapter pair used by the deterministic Mandate path.
5
+ // via the `PolicyIR` + interpreter adapter pair.
6
6
  // Flow: validate -> parseConfidence gate -> lower -> decideScope -> composeFromRecording
7
- // -> OZ compile -> (opt-in) interpreter compile + self-verify + minimise -> merge.
8
- // Same (tx, opts, ozConfig) -> byte-identical ProposedPolicy (no randomness, clock, globals).
7
+ // -> interpreter compile + self-verify.
8
+ // Same (tx, opts) -> byte-identical ProposedPolicy (no randomness, clock, globals).
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.synthesizeFromRecording = synthesizeFromRecording;
11
11
  exports.throwToolError = throwToolError;
12
12
  const adapter_ts_1 = require("../adapters/interpreter/adapter.js");
13
- const adapter_ts_2 = require("../adapters/oz/adapter.js");
14
13
  const encode_ts_1 = require("../predicate/encode.js");
15
14
  const types_ts_1 = require("../types.js");
16
15
  const compose_from_recording_ts_1 = require("./compose-from-recording.js");
17
- const deny_cases_ts_1 = require("./deny-cases.js");
18
- const evaluate_ts_1 = require("./evaluate.js");
19
- const harness_ts_1 = require("./harness.js");
20
16
  const lower_ts_1 = require("./lower.js");
21
- const minimize_ts_1 = require("./minimize.js");
22
17
  const scope_ts_1 = require("./scope.js");
23
- const UNCOVERED_PREFIX = 'Not covered by OZ built-in primitives: ';
24
- function synthesizeFromRecording(tx, opts, ozConfig) {
18
+ const UNCOVERED_PREFIX = 'Not expressible as a predicate constraint: ';
19
+ function synthesizeFromRecording(tx, opts) {
25
20
  // Convert any ToolError-shaped throw (object with a string `.code`) to a
26
21
  // structured `{ok:false, error}`; anything else is rethrown so genuine bugs
27
22
  // crash instead of being silently swallowed. Wraps the entire body so
28
- // cap errors (PREDICATE_TOO_DEEP, TOO_MANY_LEAVES) and cloneScVal depth
23
+ // cap errors (PREDICATE_TOO_DEEP, TOO_MANY_LEAVES) and encoder depth
29
24
  // throws surface as structured ToolErrors rather than RangeErrors.
30
25
  try {
31
- return synthesizeFromRecordingInner(tx, opts, ozConfig);
26
+ return synthesizeFromRecordingInner(tx, opts);
32
27
  }
33
28
  catch (e) {
34
29
  if (isToolErrorShape(e)) {
@@ -64,7 +59,7 @@ function throwToolError(code, message) {
64
59
  err.retryable = false;
65
60
  throw err;
66
61
  }
67
- function synthesizeFromRecordingInner(tx, opts, ozConfig) {
62
+ function synthesizeFromRecordingInner(tx, opts) {
68
63
  // 0. validate inputs (fail closed - never synthesize from garbage).
69
64
  const invalid = validateOptions(opts);
70
65
  if (invalid)
@@ -156,7 +151,6 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
156
151
  const topLevel = tx.invocations[0] ?? null;
157
152
  const composeOpts = {
158
153
  network: opts.network,
159
- interpreterEnabled: opts.interpreter !== undefined,
160
154
  ...(opts.userResponses !== undefined ? { userResponses: opts.userResponses } : {}),
161
155
  };
162
156
  const composed = (0, compose_from_recording_ts_1.composeFromRecording)(facts, scope.contract, topLevel, composeOpts);
@@ -164,37 +158,17 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
164
158
  // self-verify verdict (built from the SAME runHarness + evaluate that gated
165
159
  // the synthesis, not a parallel simulation).
166
160
  let explain = null;
167
- let explainSim = null;
168
- // 5. OZ compile (always runs).
169
- const ozAdapter = (0, adapter_ts_2.createOzAdapter)(ozConfig);
170
- const compileRes = ozAdapter.compile(composed.ir);
171
- if (!compileRes.proposed) {
172
- return {
173
- ok: false,
174
- error: {
175
- code: 'SYNTHESIS_ERROR',
176
- message: `recording lowered to no installable OZ policy: ${compileRes.uncovered.join('; ')}`,
177
- severity: 'error',
178
- retryable: false,
179
- details: { uncovered: compileRes.uncovered },
180
- },
181
- };
182
- }
183
161
  // 6. Interpreter compile (opt-in; fail-closed when the user routed real
184
162
  // restrictions to the interpreter).
185
163
  const interpreterOpts = opts.interpreter;
186
164
  let interpreterPolicyDocument = null;
187
165
  let interpreterPolicyRef = null;
188
- // Cross-layer L3: declared OUTSIDE the `if (interpreterOpts)` block so the
189
- // warnings folded into `proposed.warnings[]` (which lives after that block)
190
- // can read it. The block assigns it; the default is empty.
191
- let permitCtxWarnings = [];
166
+ let interpreterContextRule = null;
192
167
  if (interpreterOpts) {
193
168
  const interpreterConfig = {
194
169
  network: opts.network,
195
170
  installNonce: interpreterOpts.installNonce ?? 1,
196
171
  smartAccountAddress: interpreterOpts.smartAccountAddress,
197
- ...(interpreterOpts.oracleParams ? { oracleParams: interpreterOpts.oracleParams } : {}),
198
172
  };
199
173
  let startingPredicate = null;
200
174
  // `__testPredicateNode` is a test-only seam. It is NOT in the public
@@ -211,23 +185,19 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
211
185
  startingPredicate = testSeam;
212
186
  }
213
187
  else {
214
- const interpreterAdapter = (0, adapter_ts_1.createInterpreterAdapter)(interpreterConfig);
215
188
  let interpreterRes;
216
189
  try {
217
- interpreterRes = interpreterAdapter.compile(composed.interpreterIr);
190
+ interpreterRes = (0, adapter_ts_1.compileInterpreterPolicy)(composed.interpreterRule, interpreterConfig);
218
191
  }
219
192
  catch (e) {
220
193
  const code = e.code;
221
194
  const allowedCodes = [
222
195
  'SCOPE_SELF_CALL',
223
- 'ORACLE_LEAF_INVALID_POSITION',
224
- 'ORACLE_PARAMS_OUT_OF_RANGE',
225
196
  'MALFORMED_PREDICATE',
226
197
  'PREDICATE_TOO_LARGE',
227
198
  'PREDICATE_TOO_DEEP',
228
199
  'TOO_MANY_LEAVES',
229
200
  'IN_OPERAND_LIMIT',
230
- 'PREDICATE_ORACLE_OVER_LIMIT',
231
201
  ];
232
202
  const surfacedCode = code && allowedCodes.includes(code) ? code : 'SYNTHESIS_ERROR';
233
203
  return {
@@ -265,6 +235,7 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
265
235
  }
266
236
  interpreterPolicyDocument = interpreterRes.proposed.policyDocuments[0] ?? null;
267
237
  interpreterPolicyRef = interpreterRes.proposed.policyRefs[0] ?? null;
238
+ interpreterContextRule = interpreterRes.proposed.contextRule;
268
239
  if (!interpreterPolicyDocument || !interpreterPolicyRef) {
269
240
  return {
270
241
  ok: false,
@@ -276,7 +247,7 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
276
247
  },
277
248
  };
278
249
  }
279
- const firstInterpreterRule = composed.interpreterIr.rules[0];
250
+ const firstInterpreterRule = composed.interpreterRule;
280
251
  if (!firstInterpreterRule) {
281
252
  return {
282
253
  ok: false,
@@ -301,7 +272,9 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
301
272
  },
302
273
  };
303
274
  }
304
- // 6b. Self-verify + minimise.
275
+ // 6b. The predicate is used as compiled. There is no minimisation pass:
276
+ // nothing strips a leaf and re-checks that the reduced predicate still
277
+ // denies, so the result is NOT a proven-minimal policy.
305
278
  if (!topLevel) {
306
279
  return {
307
280
  ok: false,
@@ -324,81 +297,21 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
324
297
  },
325
298
  };
326
299
  }
327
- // Cross-layer L3: warnings collected from `buildPermitContext` (currently
328
- // only the oracle-on-right normaliser) and folded into the proposed
329
- // policy's `warnings[]` so the caller sees them on the success envelope.
330
- const permitCtxResult = buildPermitContext(tx, scope, topLevel, opts.userResponses, startingPredicate);
331
- const permitCtx = permitCtxResult.ctx;
332
- permitCtxWarnings = permitCtxResult.warnings;
333
- const finalPredicate = startingPredicate.op === 'and'
334
- ? (0, minimize_ts_1.minimize)(startingPredicate, permitCtx, deny_cases_ts_1.ORIGINAL_DIMENSIONS)
335
- : startingPredicate;
336
- const harnessCases = (0, deny_cases_ts_1.generateCases)(finalPredicate, permitCtx, deny_cases_ts_1.ORIGINAL_DIMENSIONS);
337
- const harnessResult = (0, harness_ts_1.runHarness)(finalPredicate, harnessCases);
338
- if (!harnessResult.ok) {
339
- return {
340
- ok: false,
341
- error: {
342
- code: 'DENY_CASE_FAILURE',
343
- message: `self-verify harness failed for the interpreter predicate (${harnessResult.failures.length} failure(s))`,
344
- severity: 'error',
345
- retryable: false,
346
- details: { failures: harnessResult.failures },
347
- },
348
- };
349
- }
350
- const evalResult = (0, evaluate_ts_1.evaluate)(finalPredicate, permitCtx);
351
- if (!evalResult.permit) {
352
- return {
353
- ok: false,
354
- error: {
355
- code: 'DENY_CASE_FAILURE',
356
- message: `intended recorded call was denied by the interpreter predicate: ${evalResult.reason}`,
357
- severity: 'error',
358
- retryable: false,
359
- details: { reason: evalResult.reason },
360
- },
361
- };
362
- }
363
- // --explain capture: quote the SAME verdict that gated the synthesis.
364
- // Re-evaluate each deny case to surface its concrete reason (the harness
365
- // only records whether the got-matches-expected boundary held).
300
+ const finalPredicate = startingPredicate;
366
301
  if (opts.explain) {
367
302
  explain = finalPredicate;
368
- const evaluatedCases = [
369
- {
370
- dimension: 'permit',
371
- outcome: evalResult.permit ? 'permit' : 'deny',
372
- reason: 'matches recorded call',
373
- },
374
- ];
375
- for (const deny of harnessCases.denies) {
376
- const r = (0, evaluate_ts_1.evaluate)(finalPredicate, deny.ctx);
377
- evaluatedCases.push({
378
- dimension: deny.dimension,
379
- outcome: r.permit ? 'permit' : 'deny',
380
- reason: r.permit ? 'no matching deny' : r.reason,
381
- });
382
- }
383
- explainSim = {
384
- permit: { tx: 'permit' },
385
- evaluatedCases,
386
- backend: 'ts-model',
387
- simulatorVersion: 'ts-model-1.0.0',
388
- };
389
303
  }
390
- // 6c. Re-encode the (possibly minimised) PredicateNode and stamp the
391
- // canonical bytes back onto the PolicyDocument + PolicyRef. Cap breaches
304
+ // 6c. Encode the PredicateNode and stamp the canonical bytes back onto
305
+ // the PolicyDocument + PolicyRef. Cap breaches
392
306
  // (PREDICATE_TOO_DEEP, TOO_MANY_LEAVES) throw ToolError-shaped errors;
393
307
  // the outer envelope converts them to structured `{ok:false, error}`.
394
308
  const { encodedPredicate, predicateHash } = (0, encode_ts_1.encodePredicate)(finalPredicate);
395
309
  if (testSeam !== undefined) {
396
310
  interpreterPolicyDocument = {
397
- grammarVersion: 1,
311
+ grammarVersion: types_ts_1.GRAMMAR_VERSION,
398
312
  installNonce: interpreterOpts.installNonce ?? 1,
399
313
  encodedPredicate,
400
314
  predicateHash,
401
- ...(interpreterOpts.oracleParams ? { oracleParams: interpreterOpts.oracleParams } : {}),
402
315
  };
403
316
  interpreterPolicyRef = {
404
317
  kind: 'interpreter',
@@ -431,20 +344,23 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
431
344
  }
432
345
  }
433
346
  }
434
- // When the interpreter succeeds, OZ-side `uncovered` warnings that the
435
- // interpreter actually lowered are misleading: OZ really did not lower them,
436
- // but the interpreter did. Drop those so the user-facing warnings reflect
437
- // what is still UN-enforced, not what OZ alone could not do.
438
- const ozUncovered = interpreterPolicyRef
439
- ? compileRes.uncovered.filter((u) => !INTERPRETER_COVERED_OZ_PATTERN.test(u))
440
- : compileRes.uncovered;
441
- // 7. Merge into the OZ-shaped ProposedPolicy.
442
- const ozRefs = compileRes.proposed.policyRefs;
443
- const mergedRefs = [];
444
- if (interpreterPolicyRef)
445
- mergedRefs.push(interpreterPolicyRef);
446
- for (const r of ozRefs)
447
- mergedRefs.push(r);
347
+ // 7. Assemble the ProposedPolicy. The interpreter is the only backend, so
348
+ // its context rule and policy ref are the whole policy. Without interpreter
349
+ // options nothing installable was produced, which is an error rather than
350
+ // an empty-but-successful policy.
351
+ if (!interpreterContextRule || !interpreterPolicyRef) {
352
+ return {
353
+ ok: false,
354
+ error: {
355
+ code: 'SYNTHESIS_ERROR',
356
+ message: 'no installable policy was synthesised: supply `interpreter` options (a smart account address) so the recording can be lowered to an interpreter predicate',
357
+ severity: 'error',
358
+ retryable: false,
359
+ details: { uncovered: composed.warnings },
360
+ },
361
+ };
362
+ }
363
+ const mergedRefs = [interpreterPolicyRef];
448
364
  if (mergedRefs.length > types_ts_1.OZ_LIMITS.maxPoliciesPerRule) {
449
365
  return {
450
366
  ok: false,
@@ -457,7 +373,7 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
457
373
  };
458
374
  }
459
375
  const mergedContextRule = {
460
- ...compileRes.proposed.contextRule,
376
+ ...interpreterContextRule,
461
377
  policies: mergedRefs,
462
378
  };
463
379
  // When nothing installable was synthesised (no interpreter doc AND no OZ
@@ -474,12 +390,7 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
474
390
  policyDocuments: interpreterPolicyDocument ? [interpreterPolicyDocument] : [],
475
391
  policyRefs: mergedRefs,
476
392
  parseConfidence: { ...tx.parseConfidence },
477
- warnings: [
478
- ...zeroPolicyWarning,
479
- ...ozUncovered.map((u) => `${UNCOVERED_PREFIX}${u}`),
480
- ...composed.warnings.map((w) => `${UNCOVERED_PREFIX}${w}`),
481
- ...permitCtxWarnings,
482
- ],
393
+ warnings: [...zeroPolicyWarning, ...composed.warnings.map((w) => `${UNCOVERED_PREFIX}${w}`)],
483
394
  ambiguities: mergeAmbiguities(composed.ambiguities, scope.ambiguities),
484
395
  };
485
396
  // --explain success envelope. When opts.explain is set and the OZ-only
@@ -488,40 +399,10 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
488
399
  // permit is deny with a truthful reason and evaluatedCases is empty.
489
400
  const envelope = { ok: true, data: proposed };
490
401
  if (opts.explain) {
491
- if (explainSim) {
492
- envelope.explain = { predicateTree: explain, simulation: explainSim };
493
- }
494
- else {
495
- envelope.explain = {
496
- predicateTree: null,
497
- simulation: {
498
- permit: {
499
- tx: 'deny',
500
- reason: 'No self-verification was performed (interpreter adapter was not engaged)',
501
- },
502
- evaluatedCases: [],
503
- backend: 'ts-model',
504
- simulatorVersion: 'not-run',
505
- },
506
- };
507
- }
402
+ envelope.explain = { predicateTree: explain };
508
403
  }
509
404
  return envelope;
510
405
  }
511
- /** OZ-side `uncovered` warning patterns the interpreter adapter actually lowers
512
- * when wired in. When the interpreter adapter succeeds, matching entries are
513
- * dropped from the OZ uncovered list so user-facing warnings reflect what is
514
- * still UN-enforced rather than what OZ alone could not do. Matches the
515
- * exact descriptor strings the OZ adapter emits (see `src/adapters/oz/adapter.ts`
516
- * `describeCondition` / `describeSelector`). */
517
- const INTERPRETER_COVERED_OZ_PATTERN = /^per-method scoping to|^value allowlist on arg|^exact ordered sequence on arg|^oracle price condition on|^invocation-count window|^spending_limit on token .+ needs a CallContract context scoped to that token/;
518
- /** Reject non-sane inputs before any policy is synthesized. windowSeconds /
519
- * validUntilLedger / invocationLimit must be positive integers; limitAmount a
520
- * positive i128 decimal string; network mainnet|testnet. When the interpreter
521
- * adapter is opted in, `smartAccountAddress` must be a C... contract address
522
- * (the on-chain policy-bound account, NOT the G... source account),
523
- * `installNonce` must fit u32 (default 1), and `oracleParams` must
524
- * tighten-only vs the wasm defaults. */
525
406
  function validateOptions(opts) {
526
407
  if (opts.network !== 'mainnet' && opts.network !== 'testnet') {
527
408
  return synthesisError(`network must be 'mainnet' or 'testnet', got: ${String(opts.network)}`);
@@ -534,16 +415,10 @@ function validateOptions(opts) {
534
415
  }
535
416
  const ur = opts.userResponses;
536
417
  if (ur) {
537
- if (ur.windowSeconds !== undefined && !isPositiveInt(ur.windowSeconds)) {
538
- return synthesisError(`windowSeconds must be a positive integer, got: ${ur.windowSeconds}`);
539
- }
540
418
  if (ur.validUntilLedger !== undefined &&
541
419
  (!isPositiveInt(ur.validUntilLedger) || ur.validUntilLedger > types_ts_1.SOROBAN_LIMITS.u32Max)) {
542
420
  return synthesisError(`validUntilLedger must be a positive u32 ledger sequence (<= ${types_ts_1.SOROBAN_LIMITS.u32Max}), got: ${ur.validUntilLedger}`);
543
421
  }
544
- if (ur.invocationLimit !== undefined && !isPositiveInt(ur.invocationLimit)) {
545
- return synthesisError(`invocationLimit must be a positive integer, got: ${ur.invocationLimit}`);
546
- }
547
422
  if (ur.limitAmount !== undefined && !isPositiveI128(ur.limitAmount)) {
548
423
  return synthesisError(`limitAmount must be a positive i128 decimal string within [1, ${I128_MAX}] (2^127-1), got: ${ur.limitAmount}`);
549
424
  }
@@ -567,19 +442,6 @@ function validateOptions(opts) {
567
442
  if (nonce !== undefined && (!isPositiveInt(nonce) || nonce > types_ts_1.SOROBAN_LIMITS.u32Max)) {
568
443
  return synthesisError(`interpreter.installNonce must be a positive u32 integer (<= ${types_ts_1.SOROBAN_LIMITS.u32Max}), got: ${nonce}`);
569
444
  }
570
- const op = opts.interpreter.oracleParams;
571
- if (op) {
572
- const MAX_STALE = 600;
573
- const MAX_DEV = 200;
574
- if (op.maxStalenessSeconds !== undefined &&
575
- (!isPositiveInt(op.maxStalenessSeconds) || op.maxStalenessSeconds > MAX_STALE)) {
576
- return synthesisError(`interpreter.oracleParams.maxStalenessSeconds must be a positive integer <= ${MAX_STALE} (tighten-only), got: ${op.maxStalenessSeconds}`);
577
- }
578
- if (op.maxDeviationBps !== undefined &&
579
- (!isPositiveInt(op.maxDeviationBps) || op.maxDeviationBps > MAX_DEV)) {
580
- return synthesisError(`interpreter.oracleParams.maxDeviationBps must be a positive integer <= ${MAX_DEV} (tighten-only), got: ${op.maxDeviationBps}`);
581
- }
582
- }
583
445
  }
584
446
  return null;
585
447
  }
@@ -633,180 +495,3 @@ function mergeAmbiguities(...lists) {
633
495
  }
634
496
  return out;
635
497
  }
636
- /** Build the permit `EvalContext` the self-verify harness drives. Shape
637
- * mirrors the intended recorded call so:
638
- * - `evaluate(predicate, ctx).permit === true` must hold (a failure
639
- * surfaces as DENY_CASE_FAILURE).
640
- * - `generateCases(predicate, ctx)` produces a deny battery that reflects
641
- * the actual recorded move (real amount, args, window start).
642
- * Amounts are summed per-token over all movements (BigInt, never lossy).
643
- * `oraclePriceByAsset` contains a price+timestamp satisfying each
644
- * `oracle_price` leaf so the intended call permits under every bound;
645
- * the harness mutates those entries (stale / missing / deviation / paused)
646
- * to exercise the ORACLE_* deny paths. */
647
- function buildPermitContext(tx, scope, topLevel, userResponses, predicate) {
648
- const amountByToken = {};
649
- const totals = new Map();
650
- for (const m of tx.tokenMovements) {
651
- const current = totals.get(m.token) ?? 0n;
652
- totals.set(m.token, current + BigInt(m.amount));
653
- }
654
- for (const [token, total] of totals) {
655
- amountByToken[token] = total.toString();
656
- }
657
- const warnings = [];
658
- const oraclePriceByAsset = oracleSatisfyingPrices(predicate, tx.fetchedAt, warnings);
659
- const ctx = {
660
- contract: scope.contract,
661
- fn: topLevel.fn,
662
- args: topLevel.args.map(cloneScVal),
663
- atLedger: tx.ledgerSequence,
664
- nowSeconds: tx.fetchedAt,
665
- amountByToken,
666
- windowSpentByToken: {},
667
- invocationCountByWindow: {},
668
- oraclePriceByAsset,
669
- };
670
- if (userResponses?.validUntilLedger !== undefined) {
671
- ctx.validUntilLedger = userResponses.validUntilLedger;
672
- }
673
- return { ctx, warnings };
674
- }
675
- function cloneScVal(value, depth = 0) {
676
- // Clone top-level shells so the harness can mutate deny cases without
677
- // aliasing the recorded call. Recursion bounded by MAX_SCVAL_CLONE_DEPTH so
678
- // a hand-crafted nested-vec cannot RangeError the JS stack; the over-depth
679
- // branch throws a ToolError-shaped error the envelope converts to
680
- // `{ok:false, error}`.
681
- if (value.type === 'vec') {
682
- if (depth >= types_ts_1.MAX_SCVAL_CLONE_DEPTH) {
683
- throw cloneDepthError(value);
684
- }
685
- return {
686
- type: 'vec',
687
- value: value.value.map((v) => cloneScVal(v, depth + 1)),
688
- };
689
- }
690
- return { ...value };
691
- }
692
- function cloneDepthError(value) {
693
- const err = new Error(`ScVal clone depth exceeds MAX_SCVAL_CLONE_DEPTH (${types_ts_1.MAX_SCVAL_CLONE_DEPTH})`);
694
- err.code = 'SYNTHESIS_ERROR';
695
- err.severity = 'error';
696
- err.retryable = false;
697
- err.depthContext = value.type;
698
- throw err;
699
- }
700
- /** Walk every `oracle_price` leaf and return a price map whose entries satisfy
701
- * the bound. Timestamp pinned to `nowSeconds` (the recorded `fetchedAt`) so
702
- * the fresh-oracle deny case in `generateCases` is the only path that flips
703
- * this map. Negatives clamped at 0 - oracle prices are non-negative on Stellar. */
704
- function oracleSatisfyingPrices(predicate, nowSeconds, warnings) {
705
- const out = {};
706
- visitOracleLeaves(predicate, (asset, op, bound) => {
707
- let price;
708
- switch (op) {
709
- case 'lt':
710
- case 'gt':
711
- price = op === 'lt' ? bound - 1n : bound + 1n;
712
- break;
713
- case 'lte':
714
- case 'gte':
715
- case 'eq':
716
- price = bound;
717
- break;
718
- }
719
- if (price < 0n)
720
- price = 0n;
721
- out[asset] = { price: price.toString(), timestampSeconds: nowSeconds };
722
- }, (warning) => {
723
- // Cross-layer L3: oracle-on-right that cannot be normalised is
724
- // surfaced as a warning, not silently dropped. The Rust interpreter
725
- // would surface this as `UnsupportedNode`; the warning lets the
726
- // caller decide whether to fix the predicate shape or accept the
727
- // over-permissive hole.
728
- warnings.push(warning.message);
729
- });
730
- return out;
731
- }
732
- function visitOracleLeaves(node, visit, onWarning) {
733
- switch (node.op) {
734
- case 'and':
735
- case 'or':
736
- for (const child of node.children)
737
- visitOracleLeaves(child, visit, onWarning);
738
- return;
739
- case 'not':
740
- visitOracleLeaves(node.child, visit, onWarning);
741
- return;
742
- case 'eq':
743
- case 'lt':
744
- case 'lte':
745
- case 'gt':
746
- case 'gte': {
747
- const leftLeaf = node.left;
748
- const rightLeaf = node.right;
749
- const leftIsOracle = leftLeaf.kind === 'oracle_price';
750
- const rightIsOracle = rightLeaf.kind === 'oracle_price';
751
- let oracleAsset;
752
- let literal;
753
- if (leftIsOracle) {
754
- oracleAsset = leftLeaf.asset;
755
- literal = oracleLiteralFromLeaf(rightLeaf);
756
- }
757
- else if (rightIsOracle) {
758
- oracleAsset = rightLeaf.asset;
759
- literal = oracleLiteralFromLeaf(leftLeaf);
760
- }
761
- if (oracleAsset === undefined || literal === undefined) {
762
- // Two reasons the case cannot be normalised:
763
- // - neither side is an oracle leaf (visitor has nothing to do)
764
- // - oracle-on-right with a non-literal LHS (Rust dispatch would
765
- // hit UnsupportedNode; the TS model cannot build a permit ctx
766
- // for it)
767
- // Only the second is a meaningful warning; the first is a no-op
768
- // (the visitor was called for a non-oracle comparison). Emit the
769
- // warning when an oracle IS present on one side but the other
770
- // side is not a parseable threshold literal.
771
- if (rightIsOracle) {
772
- onWarning?.({
773
- dimension: 'oracle_normalisation_dropped',
774
- op: node.op,
775
- asset: rightLeaf.kind === 'oracle_price' ? rightLeaf.asset : '',
776
- otherKind: leftLeaf.kind,
777
- message: `oracle-on-right cannot be normalised: the LHS (kind=${leftLeaf.kind}) is not an oracle_threshold literal; the Rust interpreter would surface UnsupportedNode here`,
778
- });
779
- }
780
- return;
781
- }
782
- visit(oracleAsset, node.op, literal);
783
- return;
784
- }
785
- case 'in':
786
- // `in` is pure membership; oracle leaves inside haystacks are
787
- // forbidden by the position rule, so there's nothing to set up here.
788
- return;
789
- }
790
- }
791
- /** Restate an oracle threshold on the normalised 9-dp basis prices use.
792
- * Thresholds carry their own basis, so reading the digits raw would build
793
- * a permit context off by 10^(decimals-9) and the intended call would not
794
- * satisfy its own bound. Mirrors NORMALISED_DECIMALS in oracle.rs. */
795
- function oracleLiteralFromLeaf(leaf) {
796
- if (leaf.kind !== 'oracle_threshold')
797
- return undefined;
798
- let value;
799
- try {
800
- value = BigInt(leaf.value);
801
- }
802
- catch {
803
- return undefined;
804
- }
805
- const normalised = 9n;
806
- const decimals = BigInt(leaf.decimals);
807
- if (decimals <= normalised)
808
- return value * 10n ** (normalised - decimals);
809
- // Floor: a finer-grained threshold has no exact 9-dp representation; the
810
- // caller offsets by one from here to land on the right side of the bound.
811
- return value / 10n ** (decimals - normalised);
812
- }