@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
@@ -8,8 +8,8 @@
8
8
  //
9
9
  // i128 amounts and other large integers are carried as base-10 decimal strings
10
10
  // end-to-end. Networks are pinned to the same closed set the core defines.
11
- // The discriminated union on `source` exposes BOTH synthesize_policy
12
- // front-ends through a single tool input.
11
+ // `source` is a single-variant discriminator (`recording`); it stays a union
12
+ // so an unknown value is rejected with a discriminator error.
13
13
  //
14
14
  // This module is the SINGLE source of truth for these shapes. The MCP package
15
15
  // imports them here so its tool-shape bindings stay in step; the CLI imports
@@ -98,56 +98,13 @@ export const RecordedTransactionSchema = z
98
98
  sourceAccount: z.string(),
99
99
  })
100
100
  .passthrough();
101
- /** MandateSpec mirrors the core MandateSpec. The deterministic Mandate
102
- * front-end needs no parseConfidence; the tool adapter injects the full
103
- * confidence after synthesis so the orchestrator can compare. */
104
- export const MandateSpecSchema = z
105
- .object({
106
- chain: z.literal('stellar'),
107
- contract: z.string(),
108
- method: z.string().optional(),
109
- spendingLimit: z
110
- .object({
111
- token: z.string(),
112
- limit: z.string().regex(/^[0-9]+$/),
113
- windowSeconds: z.number().int().positive(),
114
- })
115
- .optional(),
116
- // A threshold of 0 means "0 approvals", which is not a real M-of-N gate.
117
- approvalThreshold: z.number().int().positive().optional(),
118
- recipients: z.array(z.string()).optional(),
119
- expiry: z
120
- .object({
121
- validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
122
- validUntilUnixSeconds: z.number().int().positive().optional(),
123
- })
124
- .optional(),
125
- })
126
- .passthrough()
127
- // TS-F4/F6: a `recipients` allowlist is only meaningful against a SEP-41
128
- // method whose arg 1 IS the recipient (SAC/SEP-41 `transfer(from, to,
129
- // amount)` and SEP-41 `mint(to, amount)`). Without this gate the
130
- // `to-ir.ts` lowering pins the allowlist to `RECIPIENT_ARG_INDEX = 1`
131
- // for any contract+method, which would let a non-SEP-41 method's
132
- // arg[1] (e.g. an amount, an op type, an arbitrary address payload)
133
- // be silently constrained as if it were a recipient. Refusing
134
- // non-SEP-41 methods at the boundary is the fail-closed shape.
135
- .refine((v) => v.recipients === undefined ||
136
- v.recipients.length === 0 ||
137
- v.method === 'transfer' ||
138
- v.method === 'mint', {
139
- message: 'recipients is only valid when method is a SEP-41 method (transfer or mint); other methods do not have a recipient at arg[1]',
140
- });
141
- /** ComposeUserResponses mirrors the core. */
142
101
  export const ComposeUserResponsesSchema = z
143
102
  .object({
144
- windowSeconds: z.number().int().positive().optional(),
145
103
  validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
146
104
  limitAmount: z
147
105
  .string()
148
106
  .regex(/^[0-9]+$/)
149
107
  .optional(),
150
- invocationLimit: z.number().int().positive().optional(),
151
108
  // Swap recipient allowlist (SoroSwap call_arg[3]). Each entry must be a
152
109
  // Stellar address (G... wallet or C... contract); supplying it REPLACES the
153
110
  // default pin to the recorded recipient. Validated with the shared StrKey
@@ -155,33 +112,11 @@ export const ComposeUserResponsesSchema = z
155
112
  swapRecipientAllowlist: z
156
113
  .array(z.string().refine(isStellarAddress, 'must be a Stellar address (G... or C...)'))
157
114
  .optional(),
158
- // Per-asset oracle-price bound, one entry per asset. Reaches
159
- // `compose-from-recording` and emits an `oracle_price` compare. Declared
160
- // here because the object is `.passthrough()`: the field already worked
161
- // undeclared, which left the accepted surface wider than the documented
162
- // one. `decimals` is REQUIRED - oracle prices normalise to 9 dp and a
163
- // threshold silently assumed to share that basis is what let a raw 14-dp
164
- // bound permit everything.
165
- oraclePriceBound: z
166
- .array(z.object({
167
- asset: z.string().refine(isStellarAddress, 'must be a Stellar address (G... or C...)'),
168
- operator: z.enum(['eq', 'lt', 'lte', 'gt', 'gte']),
169
- value: z.string().regex(/^[0-9]+$/),
170
- decimals: z.number().int().min(0).max(U32_MAX),
171
- }))
172
- .optional(),
173
115
  })
174
- .passthrough();
175
- /** OzAdapterConfig - the per-network OZ built-in instance addresses. */
176
- export const OzAdapterConfigSchema = z.object({
177
- network: NetworkSchema,
178
- instances: z.object({
179
- spending_limit: z.string(),
180
- simple_threshold: z.string(),
181
- weighted_threshold: z.string(),
182
- }),
183
- });
184
- // ===== record_transaction =====
116
+ // Strict, not passthrough: a response key the synthesiser no longer reads
117
+ // (or a typo) would otherwise be accepted in silence, and the caller would
118
+ // believe they had answered a question that is still open.
119
+ .strict();
185
120
  export const RecordTransactionInputSchema = z
186
121
  .object({
187
122
  hash: z.string().min(1).optional(),
@@ -195,45 +130,17 @@ export const RecordTransactionInputSchema = z
195
130
  .refine((v) => Boolean(v.hash) || Boolean(v.xdr), {
196
131
  message: 'one of `hash` or `xdr` is required',
197
132
  });
198
- // ===== synthesize_policy =====
199
- //
200
- // Discriminated union on `source` exposes BOTH front-ends through ONE tool.
201
- // - `source: 'mandate'` -> calls synthesizeFromMandate
202
- // - `source: 'recording'` -> calls synthesizeFromRecording
203
- export const SynthesizePolicyMandateInputSchema = z.object({
204
- source: z.literal('mandate'),
205
- mandate: MandateSpecSchema,
206
- ozConfig: OzAdapterConfigSchema.optional(),
207
- // --explain opt-in. When true, the orchestrator attaches the
208
- // in-memory predicate tree (null for the mandate path) + a minimal
209
- // honest SimulationResult to the success envelope. Absent or false
210
- // -> the success envelope is unchanged (byte-identical to today).
211
- explain: z.boolean().optional(),
212
- });
213
- /** Interpreter opt-in for the recording path. Present -> constraints OZ cannot
214
- * express (per-method scoping, invocation-count windows, oracle bounds, exact
215
- * hop paths) lower to a real interpreter predicate document instead of being
216
- * surfaced as warnings. The core deep-validates `smartAccountAddress` (a C...
217
- * contract, not the recording's G... source) and the tighten-only oracle
218
- * bounds; the schema stays light so the core owns the friendly ToolErrors. */
219
133
  export const InterpreterOptionsSchema = z.object({
220
134
  smartAccountAddress: z.string(),
221
135
  installNonce: z.number().int().positive().optional(),
222
- oracleParams: z
223
- .object({
224
- maxStalenessSeconds: z.number().int().positive().optional(),
225
- maxDeviationBps: z.number().int().positive().optional(),
226
- })
227
- .optional(),
228
136
  });
229
- export const SynthesizePolicyRecordingInputSchema = z.object({
137
+ export const SynthesizePolicyInputSchema = z.object({
230
138
  source: z.literal('recording'),
231
139
  recordedTx: RecordedTransactionSchema,
232
140
  network: NetworkSchema,
233
141
  userResponses: ComposeUserResponsesSchema.optional(),
234
142
  confidenceOverride: z.object({ threshold: z.number().min(0).max(1) }).optional(),
235
143
  interpreter: InterpreterOptionsSchema.optional(),
236
- ozConfig: OzAdapterConfigSchema.optional(),
237
144
  // --explain opt-in. When true, the orchestrator attaches the
238
145
  // in-memory PredicateNode + the corresponding SimulationResult
239
146
  // (real one from the self-verify pipeline when the interpreter is
@@ -244,10 +151,6 @@ export const SynthesizePolicyRecordingInputSchema = z.object({
244
151
  // explain.
245
152
  explain: z.boolean().optional(),
246
153
  });
247
- export const SynthesizePolicyInputSchema = z.discriminatedUnion('source', [
248
- SynthesizePolicyMandateInputSchema,
249
- SynthesizePolicyRecordingInputSchema,
250
- ]);
251
154
  // ===== PredicateNode / PredicateLeaf =====
252
155
  //
253
156
  // Hand-written zod mirrors of the predicate grammar in types.ts:132-180.
@@ -274,41 +177,6 @@ export const PredicateLeafSchema = z.lazy(() => z.union([
274
177
  element: z.number().int().nonnegative(),
275
178
  field: z.string(),
276
179
  }),
277
- // num/den are i128 on chain; JSON numbers lose precision past 2^53,
278
- // so the wire is a decimal string. The contract refuses `den == 0` and
279
- // `num <= 0` / `den <= 0` at install (types.ts:162-163 + the install
280
- // gate); mirroring that bound here closes the gap where simulate /
281
- // verify green-light a policy the install gate will reject. The
282
- // positive-only regex is the cheapest unambiguous check.
283
- z.object({
284
- kind: z.literal('call_arg_scaled'),
285
- index: z.number().int().nonnegative(),
286
- num: z.string().regex(/^[1-9][0-9]*$/),
287
- den: z.string().regex(/^[1-9][0-9]*$/),
288
- }),
289
- z.object({ kind: z.literal('amount'), token: z.string() }),
290
- z.object({
291
- kind: z.literal('window_spent'),
292
- token: z.string(),
293
- windowSeconds: z.number().int().positive(),
294
- }),
295
- z.object({ kind: z.literal('now') }),
296
- // `valid_until` is NOT in the public predicate grammar: the encoder +
297
- // decoder already throw on it, so accepting it here would let a
298
- // hand-crafted payload through the schema and only surface a 200+
299
- // character internal-commentary error at encode time. Drop it - the
300
- // policy's expiry is carried at the install layer (MandateSpec +
301
- // validUntilLedger) instead.
302
- z.object({
303
- kind: z.literal('invocation_count_in_window'),
304
- windowSecs: z.number().int().positive(),
305
- }),
306
- z.object({ kind: z.literal('oracle_price'), asset: z.string() }),
307
- z.object({
308
- kind: z.literal('oracle_threshold'),
309
- value: z.string().regex(/^-?[0-9]+$/),
310
- decimals: z.number().int().nonnegative(),
311
- }),
312
180
  z.object({ kind: z.literal('literal_address'), value: z.string() }),
313
181
  z.object({ kind: z.literal('literal_i128'), value: z.string().regex(/^-?[0-9]+$/) }),
314
182
  z.object({ kind: z.literal('literal_symbol'), value: z.string() }),
@@ -320,36 +188,20 @@ export const PredicateLeafSchema = z.lazy(() => z.union([
320
188
  kind: z.literal('literal_u32'),
321
189
  value: z.number().int().nonnegative().max(U32_MAX),
322
190
  }),
323
- z.object({ kind: z.literal('literal_u64'), value: z.string().regex(/^[0-9]+$/) }),
324
- // `literal_bytes` is hex on chain. `Buffer.from(value, 'hex')` silently
325
- // drops non-hex chars AND yields an empty buffer when the whole input
326
- // is non-hex - so a user passing 'zzzz' would get a predicate that
327
- // compares against empty bytes instead of being rejected. The
328
- // strict even-length hex regex closes that gap at the boundary.
329
- z.object({
330
- kind: z.literal('literal_bytes'),
331
- value: z
332
- .string()
333
- .regex(/^[0-9a-fA-F]*$/)
334
- .refine((v) => v.length % 2 === 0, 'must be even-length hex'),
335
- }),
336
191
  // elements are themselves PredicateLeaf; lazy to break the cycle.
337
192
  z.object({ kind: z.literal('literal_vec'), elements: z.array(PredicateLeafSchema) }),
338
193
  ]));
339
194
  /** PredicateNode mirrors the core `PredicateNode` union. Recursive through
340
- * `and`/`or`/`not`; the lazy + annotation pattern keeps the recursion
341
- * type-safe. */
195
+ * `and`; the lazy + annotation pattern keeps the recursion type-safe. */
342
196
  export const PredicateNodeSchema = z.lazy(() => z.union([
343
197
  z.object({ op: z.literal('and'), children: z.array(PredicateNodeSchema) }),
344
- z.object({ op: z.literal('or'), children: z.array(PredicateNodeSchema) }),
345
- z.object({ op: z.literal('not'), child: PredicateNodeSchema }),
346
198
  z.object({
347
199
  op: z.literal('eq'),
348
200
  left: PredicateLeafSchema,
349
201
  right: PredicateLeafSchema,
350
202
  }),
351
203
  z.object({
352
- op: z.enum(['lt', 'lte', 'gt', 'gte']),
204
+ op: z.literal('lte'),
353
205
  left: PredicateLeafSchema,
354
206
  right: PredicateLeafSchema,
355
207
  }),
@@ -361,38 +213,16 @@ export const PredicateNodeSchema = z.lazy(() => z.union([
361
213
  ]));
362
214
  // ===== simulate_policy / verify_policy =====
363
215
  //
364
- // The oracle fixture shape mirrors `SimulateOptions` / `VerifyOptions`
365
- // in packages/policy-synth/src/verify/{simulate,verify}.ts - the same
366
- // sorted set of error enum values is used by both engines. u32
367
- // `validUntilLedger` is bounded at the boundary so a hand-crafted
368
- // payload cannot leak a u32 overflow into the engine's permit context.
369
- /** Oracle price fixture - mirror of the entry type in `SimulateOptions` /
370
- * `VerifyOptions`. Discriminated by presence of `error` vs `price`. */
371
- export const OraclePriceFixtureSchema = z.union([
372
- z.object({
373
- price: z.string().regex(/^[0-9]+$/),
374
- timestampSeconds: z.number().int().nonnegative(),
375
- }),
376
- z.object({
377
- error: z.enum(['stale', 'missing', 'deviation', 'paused', 'decimals', 'fingerprint']),
378
- }),
379
- ]);
216
+ // Both tools evaluate the same predicate against the same recording, so they
217
+ // take the same input. A null predicate used to mean "OZ built-in policies
218
+ // only"; that backend is gone, so every policy carries a predicate and there is
219
+ // nothing to simulate without one.
380
220
  export const SimulatePolicyInputSchema = z.object({
381
- // simulatePolicy accepts a null predicate (OZ-only policy); verifyPolicy
382
- // does not. The asymmetry is mirrored at the schema boundary.
383
- predicate: PredicateNodeSchema.nullable(),
384
- permitTx: RecordedTransactionSchema,
385
- validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
386
- oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
387
- });
388
- export const VerifyPolicyInputSchema = z.object({
389
- // verifyPolicy requires a non-null predicate; the engine refuses
390
- // `null` outright. The schema mirrors that contract.
391
221
  predicate: PredicateNodeSchema,
392
222
  permitTx: RecordedTransactionSchema,
393
223
  validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
394
- oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
395
224
  });
225
+ export const VerifyPolicyInputSchema = SimulatePolicyInputSchema;
396
226
  // ===== install_policy / revoke_policy / get_interpreter_info =====
397
227
  //
398
228
  // The install/revoke pair drives the OZ smart-account admin flow. Both surface
@@ -405,7 +235,7 @@ export const VerifyPolicyInputSchema = z.object({
405
235
  // optional `grammar_version()` RPC call to check whether the deployed contract
406
236
  // matches the pin. A mismatch is worth MORE than a fabricated audit field.
407
237
  //
408
- // All three input schemas live BELOW the MandateSpecSchemaForRule declaration
238
+ // All three input schemas live BELOW the ContextRuleDraftSchema declaration
409
239
  // so the const reference there is not in the temporal dead zone (no JS hoisting
410
240
  // for `const`).
411
241
  /** Minimal rule-draft schema for the install input. Mirrors ContextRuleDraft
@@ -416,7 +246,7 @@ export const VerifyPolicyInputSchema = z.object({
416
246
  * the temporal dead zone (no JS hoisting for `const`). */
417
247
  const MAX_SIGNERS_PER_RULE = 15;
418
248
  const MAX_POLICIES_PER_RULE = 5;
419
- const MandateSpecSchemaForRule = z
249
+ const ContextRuleDraftSchema = z
420
250
  .object({
421
251
  contextRuleType: z.discriminatedUnion('kind', [
422
252
  z.object({ kind: z.literal('default') }),
@@ -436,21 +266,11 @@ const MandateSpecSchemaForRule = z
436
266
  ]))
437
267
  .max(MAX_SIGNERS_PER_RULE),
438
268
  policies: z
439
- .array(z.discriminatedUnion('kind', [
440
- z.object({
441
- kind: z.literal('interpreter'),
442
- interpreterAddress: z.string(),
443
- predicateBlobBase64: z.string().min(1),
444
- }),
445
- z.object({
446
- kind: z.literal('oz_builtin'),
447
- primitive: z.object({
448
- primitive: z.enum(['spending_limit', 'simple_threshold', 'weighted_threshold']),
449
- params: z.record(z.unknown()),
450
- }),
451
- instanceAddress: z.string(),
452
- }),
453
- ]))
269
+ .array(z.object({
270
+ kind: z.literal('interpreter'),
271
+ interpreterAddress: z.string(),
272
+ predicateBlobBase64: z.string().min(1),
273
+ }))
454
274
  .max(MAX_POLICIES_PER_RULE),
455
275
  })
456
276
  .passthrough()
@@ -460,27 +280,23 @@ const MandateSpecSchemaForRule = z
460
280
  // existing four.
461
281
  /** Pinned interpreter address (testnet).
462
282
  * Single source for the MCP layer; do not embed elsewhere. */
463
- export const PINNED_INTERPRETER_TESTNET_ADDRESS = 'CALHNU4LXZRKFAXYRBODTGDANTNXHJPBTJNYGLUWLIFIP24NWSGWIE4K';
464
- /** Pinned interpreter address (mainnet), deployed 2026-08-04.
465
- * UNAUDITED at the time of writing. */
466
- export const PINNED_INTERPRETER_MAINNET_ADDRESS = 'CALZAMUPREIRY4TULBEXIK77AUTOEJG63XLCPUWEHHQDOVK6ZVVS7VQ2';
467
- /** Pinned interpreter wasm sha256 (hex), mainnet.
283
+ export const PINNED_INTERPRETER_TESTNET_ADDRESS = 'CCL336TCK2Y5OFNRCMN2M3HVPBCEX4PW5H6EQ5VW5NPMXOCP4ESB5XR4';
284
+ /** Pinned interpreter address (mainnet), redeployed 2026-08-22 from a reproducible build. The mainnet
285
+ * interpreter IS the binary exercised on testnet - both instances were created
286
+ * from the same uploaded wasm hash (see PINNED_INTERPRETER_WASM_SHA256), and
287
+ * both were read back with `grammar_version()` returning 3. The address differs
288
+ * because instance ids are network-scoped. UNAUDITED at the time of writing.
468
289
  *
469
- * The two networks no longer run the same binary. Testnet carries the
470
- * selector-leaf minimum and the signer-set cap; mainnet predates both. Read
471
- * the hash through `PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK` rather than
472
- * this constant unless mainnet is specifically what is meant, or
473
- * `get_interpreter_info` will report a hash the queried network does not
474
- * run. */
475
- export const PINNED_INTERPRETER_WASM_SHA256 = '6e6c13d93e197aa380303a42cd120f5ddb080dd36ef2a343ee1dbd04ca52a443';
476
- /** Pinned interpreter wasm sha256 (hex), testnet. Byte-identical to the
477
- * artifact built from `contracts/policy-interpreter` at the commit that
478
- * introduced errors 216 and 217, verified by fetching the deployed wasm back
479
- * off chain. */
480
- export const PINNED_INTERPRETER_WASM_SHA256_TESTNET = 'a4d58bc88fd82bbb8e223941ba5889db919717fae92ced13f6f55bfe583b8e22';
290
+ * These four constants move together or not at all. The grammar version and
291
+ * wasm hash are single values covering BOTH networks, so re-pinning one network
292
+ * alone would have the builder emit a version the other network refuses - with
293
+ * a green test run, since `grammar-version-parity.test.ts` would then pass. */
294
+ export const PINNED_INTERPRETER_MAINNET_ADDRESS = 'CBZXLSTQUITBFZHQH6XRXF3XIVRQR4RHRI64Q5WELS5KGY3ZKJPFWDPF';
295
+ /** Pinned interpreter wasm sha256 (hex). */
296
+ export const PINNED_INTERPRETER_WASM_SHA256 = 'a2b36e8ac5a61caf3757af26aa79e83f2995b451099f44772383806a55fe3414';
481
297
  /** The grammar version the interpreter enforces (matches SELF_VERSION in
482
298
  * contracts/policy-interpreter/src/version.rs). */
483
- export const PINNED_INTERPRETER_GRAMMAR_VERSION = 1;
299
+ export const PINNED_INTERPRETER_GRAMMAR_VERSION = 3;
484
300
  /** Default Soroban RPC for the install / revoke / info tools. The recorder
485
301
  * keeps its own copy in record/rpc.ts because it hands back a fetcher rather
486
302
  * than a Server; the two are deliberately different surfaces, so this is
@@ -491,25 +307,18 @@ export const TESTNET_RPC_URL = 'https://soroban-testnet.stellar.org';
491
307
  * RPC rather than always testnet. Public mainnet endpoint, the same one
492
308
  * the deploy script hit during the 2026-08-04 mainnet rollout. */
493
309
  export const MAINNET_RPC_URL = 'https://mainnet.sorobanrpc.com';
494
- /** Pin + RPC lookup for the gate enforcement. Addresses, RPCs and wasm
495
- * hashes are all network-scoped: the networks diverged when the
496
- * selector-leaf and signer-cap controls were deployed to testnet ahead of
497
- * mainnet. */
310
+ /** Pin + RPC lookup for the gate enforcement. The interpreters' wasm sha256
311
+ * is identical across both networks (the same binary was uploaded both
312
+ * places), so `PINNED_INTERPRETER_WASM_SHA256` stays
313
+ * a single constant - only the addresses and RPCs are network-scoped. */
498
314
  export const PINNED_INTERPRETER_ADDRESS_BY_NETWORK = {
499
315
  testnet: PINNED_INTERPRETER_TESTNET_ADDRESS,
500
316
  mainnet: PINNED_INTERPRETER_MAINNET_ADDRESS,
501
317
  };
502
- export const PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK = {
503
- testnet: PINNED_INTERPRETER_WASM_SHA256_TESTNET,
504
- mainnet: PINNED_INTERPRETER_WASM_SHA256,
505
- };
506
318
  export const RPC_URL_BY_NETWORK = {
507
319
  testnet: TESTNET_RPC_URL,
508
320
  mainnet: MAINNET_RPC_URL,
509
321
  };
510
- /** Soroban `valid_until` window (ledgers) added to the latest ledger when
511
- * building the auth entry. ~25 minutes at 5s/ledger. */
512
- export const DEFAULT_AUTH_VALID_UNTIL_LEDGERS = 300;
513
322
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
514
323
  * matching passphrase when the wallet signs (a mismatch yields invalid
515
324
  * hashes). */
@@ -546,7 +355,7 @@ export const InstallPolicyInputSchema = z
546
355
  * pin and RPC pin do not move by themselves). */
547
356
  network: NetworkSchema.optional(),
548
357
  /** The proposed rule draft. Mirrors the core `ContextRuleDraft` shape. */
549
- rule: MandateSpecSchemaForRule,
358
+ rule: ContextRuleDraftSchema,
550
359
  /** Per-rule install nonce; 1 for a fresh install. */
551
360
  installNonce: z.number().int().positive(),
552
361
  /** Optional RPC URL override. Defaults to the pinned RPC for the
@@ -566,50 +375,12 @@ export const InstallPolicyInputSchema = z
566
375
  * everything. Selecting `network: 'mainnet'` is NOT an opt-in -
567
376
  * the mainnet pin is its own deny-by-default anchor. */
568
377
  allowUnpinnedInterpreter: z.boolean().optional(),
569
- /** Opt-in to installing when a signer of this rule can already reach the
570
- * same calls through a context rule that has NO policy attached. OZ lets
571
- * the signer choose which rule authorises a call and enforces only that
572
- * rule's policies, so an unpoliced rule covering the same calls makes
573
- * this policy decorative. Default-deny, because the caller almost
574
- * certainly believes they are restricting something. */
575
- allowAuthorityOverlap: z.boolean().optional(),
576
- /** Skip the cross-rule authority scan entirely. The scan costs one RPC
577
- * read per rule on the account; skipping it means the response carries
578
- * no statement about what the signers can already do. */
579
- skipAuthorityScan: z.boolean().optional(),
580
378
  /** Base fee in stroops; defaults to BASE_FEE (100). */
581
379
  baseFee: z.number().int().positive().optional(),
582
380
  })
583
381
  .refine((v) => Boolean(v.smartAccount) && Boolean(v.sourceAccount), {
584
382
  message: 'smartAccount and sourceAccount are required',
585
383
  });
586
- /** `merge_policy` input.
587
- *
588
- * The tightening remedy for a cross-rule overlap: replace a rule's predicate
589
- * with the conjunction of it and a new one. Two transactions, in order,
590
- * because OZ refuses to re-attach a policy already on the rule - so the
591
- * caller runs `detach`, waits for it to confirm, then runs `reinstall`. */
592
- export const MergePolicyInputSchema = z
593
- .object({
594
- smartAccount: z
595
- .string()
596
- .regex(STELLAR_CONTRACT_ADDRESS, 'smartAccount must be a Stellar contract address (C...)'),
597
- sourceAccount: z
598
- .string()
599
- .regex(STELLAR_ACCOUNT_ADDRESS, 'sourceAccount must be a Stellar account address (G...)'),
600
- /** The rule whose predicate is being tightened. */
601
- ruleId: z.number().int().nonnegative(),
602
- /** The predicate to conjoin, base64 canonical ScVal, as emitted by
603
- * `synthesize_policy`. */
604
- incomingPredicateBlobBase64: z.string().min(1),
605
- /** Which half of the remedy to build. */
606
- step: z.enum(['detach', 'reinstall']),
607
- network: NetworkSchema.optional(),
608
- rpcUrl: z.string().url().optional(),
609
- allowUnpinnedRpcUrl: z.boolean().optional(),
610
- baseFee: z.number().int().positive().optional(),
611
- })
612
- .strict();
613
384
  export const RevokePolicyInputSchema = z
614
385
  .object({
615
386
  /** The smart account contract address (C...). */
@@ -13,17 +13,13 @@ export interface GeneratedCases {
13
13
  permit: EvalContext;
14
14
  denies: DenyCase[];
15
15
  }
16
- declare const OVERPERMISSIVE_DIMENSIONS: readonly ["argument_reorder"];
17
- declare const ORIGINAL_DIMENSIONS: string[];
18
- export { ORIGINAL_DIMENSIONS, OVERPERMISSIVE_DIMENSIONS };
19
16
  /** Build deterministic model-evaluated alternatives without mutating the intended call.
20
17
  *
21
18
  * @param predicate - the synthesized predicate
22
- * @param permitCtx - EvalContext for the intended (permitted) call
23
- * @param dimensions - optional whitelist of dimension names to emit; when omitted
24
- * all known dimensions (including over-permissiveness mutations)
25
- * are emitted. The synth pipeline passes ORIGINAL_DIMENSIONS so
26
- * existing fixtures do not regress. */
19
+ * @param permitCtx - EvalContext for the intended (permitted) call
20
+ * @param dimensions - optional whitelist of dimension names to emit; when omitted
21
+ * all known dimensions (including over-permissiveness mutations)
22
+ * are emitted. */
27
23
  export declare function generateCases(predicate: PredicateNode, permitCtx: EvalContext, dimensions?: string[]): GeneratedCases;
28
24
  /** Deep-copy an ScVal so a mutation cannot alias the recorded call.
29
25
  * Exported so the permit-context builder shares this one implementation. */