@crediolabs/policy-synth 0.1.18 → 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 (159) hide show
  1. package/README.md +6 -5
  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 -11
  11. package/dist/install/build-add-context-rule.js +2 -97
  12. package/dist/install/build-install-policy.d.ts +4 -53
  13. package/dist/install/build-install-policy.js +44 -123
  14. package/dist/ir/index.d.ts +1 -1
  15. package/dist/ir/types.d.ts +7 -76
  16. package/dist/ir/types.js +0 -2
  17. package/dist/predicate/decode.d.ts +1 -1
  18. package/dist/predicate/decode.js +2 -70
  19. package/dist/predicate/encode.js +45 -229
  20. package/dist/predicate/from-json.js +1 -42
  21. package/dist/record/decode.js +3 -8
  22. package/dist/review-card/builder.d.ts +4 -3
  23. package/dist/review-card/builder.js +18 -162
  24. package/dist/review-card/cross-check.js +2 -105
  25. package/dist/review-card/render-leaf.d.ts +4 -0
  26. package/dist/review-card/render-leaf.js +47 -0
  27. package/dist/run/index.d.ts +35 -53
  28. package/dist/run/index.js +115 -156
  29. package/dist/run/schemas.d.ts +168 -1650
  30. package/dist/run/schemas.js +36 -199
  31. package/dist/seams/types.d.ts +0 -2
  32. package/dist/simulate/deny-cases.d.ts +26 -0
  33. package/dist/simulate/deny-cases.js +282 -0
  34. package/dist/simulate/evaluate.d.ts +18 -0
  35. package/dist/simulate/evaluate.js +271 -0
  36. package/dist/simulate/index.d.ts +4 -0
  37. package/dist/simulate/index.js +8 -0
  38. package/dist/synth/compose-from-recording.d.ts +24 -51
  39. package/dist/synth/compose-from-recording.js +120 -282
  40. package/dist/synth/deny-cases.d.ts +5 -7
  41. package/dist/synth/deny-cases.js +23 -373
  42. package/dist/synth/evaluate.d.ts +0 -21
  43. package/dist/synth/evaluate.js +56 -336
  44. package/dist/synth/index.d.ts +0 -5
  45. package/dist/synth/index.js +0 -5
  46. package/dist/synth/permit-context.d.ts +2 -9
  47. package/dist/synth/permit-context.js +1 -82
  48. package/dist/synth/synthesize-from-recording.d.ts +1 -29
  49. package/dist/synth/synthesize-from-recording.js +41 -356
  50. package/dist/types.d.ts +8 -60
  51. package/dist/types.js +5 -2
  52. package/dist/verify/simulate.d.ts +1 -14
  53. package/dist/verify/simulate.js +2 -88
  54. package/dist/verify/verify.d.ts +1 -7
  55. package/dist/verify/verify.js +0 -8
  56. package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
  57. package/dist-cjs/adapters/interpreter/adapter.js +56 -489
  58. package/dist-cjs/adapters/interpreter/index.d.ts +1 -1
  59. package/dist-cjs/adapters/interpreter/index.js +1 -2
  60. package/dist-cjs/adapters/oz/adapter.js +1 -15
  61. package/dist-cjs/errors.d.ts +1 -1
  62. package/dist-cjs/index.d.ts +1 -4
  63. package/dist-cjs/index.js +1 -4
  64. package/dist-cjs/install/build-add-context-rule.d.ts +3 -11
  65. package/dist-cjs/install/build-add-context-rule.js +1 -96
  66. package/dist-cjs/install/build-install-policy.d.ts +4 -53
  67. package/dist-cjs/install/build-install-policy.js +44 -122
  68. package/dist-cjs/ir/index.d.ts +1 -1
  69. package/dist-cjs/ir/types.d.ts +7 -76
  70. package/dist-cjs/ir/types.js +0 -2
  71. package/dist-cjs/predicate/decode.d.ts +1 -1
  72. package/dist-cjs/predicate/decode.js +2 -70
  73. package/dist-cjs/predicate/encode.js +45 -229
  74. package/dist-cjs/predicate/from-json.js +1 -42
  75. package/dist-cjs/record/decode.js +3 -8
  76. package/dist-cjs/review-card/builder.d.ts +4 -3
  77. package/dist-cjs/review-card/builder.js +23 -167
  78. package/dist-cjs/review-card/cross-check.js +7 -110
  79. package/dist-cjs/review-card/render-leaf.d.ts +4 -0
  80. package/dist-cjs/review-card/render-leaf.js +52 -0
  81. package/dist-cjs/run/index.d.ts +35 -53
  82. package/dist-cjs/run/index.js +116 -162
  83. package/dist-cjs/run/schemas.d.ts +168 -1650
  84. package/dist-cjs/run/schemas.js +37 -200
  85. package/dist-cjs/seams/types.d.ts +0 -2
  86. package/dist-cjs/simulate/deny-cases.d.ts +26 -0
  87. package/dist-cjs/simulate/deny-cases.js +286 -0
  88. package/dist-cjs/simulate/evaluate.d.ts +18 -0
  89. package/dist-cjs/simulate/evaluate.js +274 -0
  90. package/dist-cjs/simulate/index.d.ts +4 -0
  91. package/dist-cjs/simulate/index.js +13 -0
  92. package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
  93. package/dist-cjs/synth/compose-from-recording.js +120 -282
  94. package/dist-cjs/synth/deny-cases.d.ts +5 -7
  95. package/dist-cjs/synth/deny-cases.js +23 -374
  96. package/dist-cjs/synth/evaluate.d.ts +0 -21
  97. package/dist-cjs/synth/evaluate.js +57 -337
  98. package/dist-cjs/synth/index.d.ts +0 -5
  99. package/dist-cjs/synth/index.js +1 -11
  100. package/dist-cjs/synth/permit-context.d.ts +2 -9
  101. package/dist-cjs/synth/permit-context.js +1 -82
  102. package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
  103. package/dist-cjs/synth/synthesize-from-recording.js +39 -354
  104. package/dist-cjs/types.d.ts +8 -60
  105. package/dist-cjs/types.js +6 -3
  106. package/dist-cjs/verify/simulate.d.ts +1 -14
  107. package/dist-cjs/verify/simulate.js +2 -88
  108. package/dist-cjs/verify/verify.d.ts +1 -7
  109. package/dist-cjs/verify/verify.js +0 -8
  110. package/package.json +4 -4
  111. package/src/adapters/interpreter/adapter.ts +76 -572
  112. package/src/errors.ts +0 -19
  113. package/src/index.ts +1 -4
  114. package/src/install/build-add-context-rule.ts +4 -127
  115. package/src/install/build-install-policy.ts +93 -214
  116. package/src/predicate/decode.ts +2 -70
  117. package/src/predicate/encode.ts +49 -261
  118. package/src/predicate/from-json.ts +1 -43
  119. package/src/record/decode.ts +3 -8
  120. package/src/review-card/builder.ts +19 -168
  121. package/src/review-card/cross-check.ts +3 -105
  122. package/src/review-card/render-leaf.ts +48 -0
  123. package/src/run/index.ts +130 -186
  124. package/src/run/schemas.ts +38 -214
  125. package/src/simulate/deny-cases.ts +334 -0
  126. package/src/simulate/evaluate.ts +284 -0
  127. package/src/simulate/index.ts +11 -0
  128. package/src/synth/compose-from-recording.ts +148 -347
  129. package/src/synth/index.ts +0 -13
  130. package/src/synth/synthesize-from-recording.ts +43 -456
  131. package/src/types.ts +13 -49
  132. package/dist/install/build-install-predicate.d.ts +0 -96
  133. package/dist/install/build-install-predicate.js +0 -444
  134. package/dist-cjs/install/build-install-predicate.d.ts +0 -96
  135. package/dist-cjs/install/build-install-predicate.js +0 -479
  136. package/src/adapters/interpreter/index.ts +0 -8
  137. package/src/adapters/oz/adapter.ts +0 -376
  138. package/src/adapters/oz/index.ts +0 -9
  139. package/src/codegen/compile-gate.ts +0 -167
  140. package/src/codegen/index.ts +0 -17
  141. package/src/codegen/template.ts +0 -165
  142. package/src/ir/index.ts +0 -13
  143. package/src/ir/types.ts +0 -132
  144. package/src/mandate/index.ts +0 -4
  145. package/src/mandate/to-ir.ts +0 -71
  146. package/src/mandate/types.ts +0 -21
  147. package/src/seams/index.ts +0 -11
  148. package/src/seams/types.ts +0 -81
  149. package/src/synth/deny-cases.ts +0 -663
  150. package/src/synth/evaluate.ts +0 -613
  151. package/src/synth/harness.ts +0 -68
  152. package/src/synth/minimize.ts +0 -48
  153. package/src/synth/permit-context.ts +0 -136
  154. package/src/synth/predicate-literals.ts +0 -27
  155. package/src/synth/synthesize-from-mandate.ts +0 -82
  156. package/src/verify/envelope.ts +0 -28
  157. package/src/verify/index.ts +0 -5
  158. package/src/verify/simulate.ts +0 -311
  159. 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
@@ -156,17 +113,10 @@ export const ComposeUserResponsesSchema = z
156
113
  .array(z.string().refine(isStellarAddress, 'must be a Stellar address (G... or C...)'))
157
114
  .optional(),
158
115
  })
159
- .passthrough();
160
- /** OzAdapterConfig - the per-network OZ built-in instance addresses. */
161
- export const OzAdapterConfigSchema = z.object({
162
- network: NetworkSchema,
163
- instances: z.object({
164
- spending_limit: z.string(),
165
- simple_threshold: z.string(),
166
- weighted_threshold: z.string(),
167
- }),
168
- });
169
- // ===== 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();
170
120
  export const RecordTransactionInputSchema = z
171
121
  .object({
172
122
  hash: z.string().min(1).optional(),
@@ -180,45 +130,17 @@ export const RecordTransactionInputSchema = z
180
130
  .refine((v) => Boolean(v.hash) || Boolean(v.xdr), {
181
131
  message: 'one of `hash` or `xdr` is required',
182
132
  });
183
- // ===== synthesize_policy =====
184
- //
185
- // Discriminated union on `source` exposes BOTH front-ends through ONE tool.
186
- // - `source: 'mandate'` -> calls synthesizeFromMandate
187
- // - `source: 'recording'` -> calls synthesizeFromRecording
188
- export const SynthesizePolicyMandateInputSchema = z.object({
189
- source: z.literal('mandate'),
190
- mandate: MandateSpecSchema,
191
- ozConfig: OzAdapterConfigSchema.optional(),
192
- // --explain opt-in. When true, the orchestrator attaches the
193
- // in-memory predicate tree (null for the mandate path) + a minimal
194
- // honest SimulationResult to the success envelope. Absent or false
195
- // -> the success envelope is unchanged (byte-identical to today).
196
- explain: z.boolean().optional(),
197
- });
198
- /** Interpreter opt-in for the recording path. Present -> constraints OZ cannot
199
- * express (per-method scoping, invocation-count windows, oracle bounds, exact
200
- * hop paths) lower to a real interpreter predicate document instead of being
201
- * surfaced as warnings. The core deep-validates `smartAccountAddress` (a C...
202
- * contract, not the recording's G... source) and the tighten-only oracle
203
- * bounds; the schema stays light so the core owns the friendly ToolErrors. */
204
133
  export const InterpreterOptionsSchema = z.object({
205
134
  smartAccountAddress: z.string(),
206
135
  installNonce: z.number().int().positive().optional(),
207
- oracleParams: z
208
- .object({
209
- maxStalenessSeconds: z.number().int().positive().optional(),
210
- maxDeviationBps: z.number().int().positive().optional(),
211
- })
212
- .optional(),
213
136
  });
214
- export const SynthesizePolicyRecordingInputSchema = z.object({
137
+ export const SynthesizePolicyInputSchema = z.object({
215
138
  source: z.literal('recording'),
216
139
  recordedTx: RecordedTransactionSchema,
217
140
  network: NetworkSchema,
218
141
  userResponses: ComposeUserResponsesSchema.optional(),
219
142
  confidenceOverride: z.object({ threshold: z.number().min(0).max(1) }).optional(),
220
143
  interpreter: InterpreterOptionsSchema.optional(),
221
- ozConfig: OzAdapterConfigSchema.optional(),
222
144
  // --explain opt-in. When true, the orchestrator attaches the
223
145
  // in-memory PredicateNode + the corresponding SimulationResult
224
146
  // (real one from the self-verify pipeline when the interpreter is
@@ -229,10 +151,6 @@ export const SynthesizePolicyRecordingInputSchema = z.object({
229
151
  // explain.
230
152
  explain: z.boolean().optional(),
231
153
  });
232
- export const SynthesizePolicyInputSchema = z.discriminatedUnion('source', [
233
- SynthesizePolicyMandateInputSchema,
234
- SynthesizePolicyRecordingInputSchema,
235
- ]);
236
154
  // ===== PredicateNode / PredicateLeaf =====
237
155
  //
238
156
  // Hand-written zod mirrors of the predicate grammar in types.ts:132-180.
@@ -259,41 +177,6 @@ export const PredicateLeafSchema = z.lazy(() => z.union([
259
177
  element: z.number().int().nonnegative(),
260
178
  field: z.string(),
261
179
  }),
262
- // num/den are i128 on chain; JSON numbers lose precision past 2^53,
263
- // so the wire is a decimal string. The contract refuses `den == 0` and
264
- // `num <= 0` / `den <= 0` at install (types.ts:162-163 + the install
265
- // gate); mirroring that bound here closes the gap where simulate /
266
- // verify green-light a policy the install gate will reject. The
267
- // positive-only regex is the cheapest unambiguous check.
268
- z.object({
269
- kind: z.literal('call_arg_scaled'),
270
- index: z.number().int().nonnegative(),
271
- num: z.string().regex(/^[1-9][0-9]*$/),
272
- den: z.string().regex(/^[1-9][0-9]*$/),
273
- }),
274
- z.object({ kind: z.literal('amount'), token: z.string() }),
275
- z.object({
276
- kind: z.literal('window_spent'),
277
- token: z.string(),
278
- windowSeconds: z.number().int().positive(),
279
- }),
280
- z.object({ kind: z.literal('now') }),
281
- // `valid_until` is NOT in the public predicate grammar: the encoder +
282
- // decoder already throw on it, so accepting it here would let a
283
- // hand-crafted payload through the schema and only surface a 200+
284
- // character internal-commentary error at encode time. Drop it - the
285
- // policy's expiry is carried at the install layer (MandateSpec +
286
- // validUntilLedger) instead.
287
- z.object({
288
- kind: z.literal('invocation_count_in_window'),
289
- windowSecs: z.number().int().positive(),
290
- }),
291
- z.object({ kind: z.literal('oracle_price'), asset: z.string() }),
292
- z.object({
293
- kind: z.literal('oracle_threshold'),
294
- value: z.string().regex(/^-?[0-9]+$/),
295
- decimals: z.number().int().nonnegative(),
296
- }),
297
180
  z.object({ kind: z.literal('literal_address'), value: z.string() }),
298
181
  z.object({ kind: z.literal('literal_i128'), value: z.string().regex(/^-?[0-9]+$/) }),
299
182
  z.object({ kind: z.literal('literal_symbol'), value: z.string() }),
@@ -305,36 +188,20 @@ export const PredicateLeafSchema = z.lazy(() => z.union([
305
188
  kind: z.literal('literal_u32'),
306
189
  value: z.number().int().nonnegative().max(U32_MAX),
307
190
  }),
308
- z.object({ kind: z.literal('literal_u64'), value: z.string().regex(/^[0-9]+$/) }),
309
- // `literal_bytes` is hex on chain. `Buffer.from(value, 'hex')` silently
310
- // drops non-hex chars AND yields an empty buffer when the whole input
311
- // is non-hex - so a user passing 'zzzz' would get a predicate that
312
- // compares against empty bytes instead of being rejected. The
313
- // strict even-length hex regex closes that gap at the boundary.
314
- z.object({
315
- kind: z.literal('literal_bytes'),
316
- value: z
317
- .string()
318
- .regex(/^[0-9a-fA-F]*$/)
319
- .refine((v) => v.length % 2 === 0, 'must be even-length hex'),
320
- }),
321
191
  // elements are themselves PredicateLeaf; lazy to break the cycle.
322
192
  z.object({ kind: z.literal('literal_vec'), elements: z.array(PredicateLeafSchema) }),
323
193
  ]));
324
194
  /** PredicateNode mirrors the core `PredicateNode` union. Recursive through
325
- * `and`/`or`/`not`; the lazy + annotation pattern keeps the recursion
326
- * type-safe. */
195
+ * `and`; the lazy + annotation pattern keeps the recursion type-safe. */
327
196
  export const PredicateNodeSchema = z.lazy(() => z.union([
328
197
  z.object({ op: z.literal('and'), children: z.array(PredicateNodeSchema) }),
329
- z.object({ op: z.literal('or'), children: z.array(PredicateNodeSchema) }),
330
- z.object({ op: z.literal('not'), child: PredicateNodeSchema }),
331
198
  z.object({
332
199
  op: z.literal('eq'),
333
200
  left: PredicateLeafSchema,
334
201
  right: PredicateLeafSchema,
335
202
  }),
336
203
  z.object({
337
- op: z.enum(['lt', 'lte', 'gt', 'gte']),
204
+ op: z.literal('lte'),
338
205
  left: PredicateLeafSchema,
339
206
  right: PredicateLeafSchema,
340
207
  }),
@@ -346,38 +213,16 @@ export const PredicateNodeSchema = z.lazy(() => z.union([
346
213
  ]));
347
214
  // ===== simulate_policy / verify_policy =====
348
215
  //
349
- // The oracle fixture shape mirrors `SimulateOptions` / `VerifyOptions`
350
- // in packages/policy-synth/src/verify/{simulate,verify}.ts - the same
351
- // sorted set of error enum values is used by both engines. u32
352
- // `validUntilLedger` is bounded at the boundary so a hand-crafted
353
- // payload cannot leak a u32 overflow into the engine's permit context.
354
- /** Oracle price fixture - mirror of the entry type in `SimulateOptions` /
355
- * `VerifyOptions`. Discriminated by presence of `error` vs `price`. */
356
- export const OraclePriceFixtureSchema = z.union([
357
- z.object({
358
- price: z.string().regex(/^[0-9]+$/),
359
- timestampSeconds: z.number().int().nonnegative(),
360
- }),
361
- z.object({
362
- error: z.enum(['stale', 'missing', 'deviation', 'paused', 'decimals', 'fingerprint']),
363
- }),
364
- ]);
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.
365
220
  export const SimulatePolicyInputSchema = z.object({
366
- // simulatePolicy accepts a null predicate (OZ-only policy); verifyPolicy
367
- // does not. The asymmetry is mirrored at the schema boundary.
368
- predicate: PredicateNodeSchema.nullable(),
369
- permitTx: RecordedTransactionSchema,
370
- validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
371
- oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
372
- });
373
- export const VerifyPolicyInputSchema = z.object({
374
- // verifyPolicy requires a non-null predicate; the engine refuses
375
- // `null` outright. The schema mirrors that contract.
376
221
  predicate: PredicateNodeSchema,
377
222
  permitTx: RecordedTransactionSchema,
378
223
  validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
379
- oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
380
224
  });
225
+ export const VerifyPolicyInputSchema = SimulatePolicyInputSchema;
381
226
  // ===== install_policy / revoke_policy / get_interpreter_info =====
382
227
  //
383
228
  // The install/revoke pair drives the OZ smart-account admin flow. Both surface
@@ -390,7 +235,7 @@ export const VerifyPolicyInputSchema = z.object({
390
235
  // optional `grammar_version()` RPC call to check whether the deployed contract
391
236
  // matches the pin. A mismatch is worth MORE than a fabricated audit field.
392
237
  //
393
- // All three input schemas live BELOW the MandateSpecSchemaForRule declaration
238
+ // All three input schemas live BELOW the ContextRuleDraftSchema declaration
394
239
  // so the const reference there is not in the temporal dead zone (no JS hoisting
395
240
  // for `const`).
396
241
  /** Minimal rule-draft schema for the install input. Mirrors ContextRuleDraft
@@ -401,7 +246,7 @@ export const VerifyPolicyInputSchema = z.object({
401
246
  * the temporal dead zone (no JS hoisting for `const`). */
402
247
  const MAX_SIGNERS_PER_RULE = 15;
403
248
  const MAX_POLICIES_PER_RULE = 5;
404
- const MandateSpecSchemaForRule = z
249
+ const ContextRuleDraftSchema = z
405
250
  .object({
406
251
  contextRuleType: z.discriminatedUnion('kind', [
407
252
  z.object({ kind: z.literal('default') }),
@@ -421,21 +266,11 @@ const MandateSpecSchemaForRule = z
421
266
  ]))
422
267
  .max(MAX_SIGNERS_PER_RULE),
423
268
  policies: z
424
- .array(z.discriminatedUnion('kind', [
425
- z.object({
426
- kind: z.literal('interpreter'),
427
- interpreterAddress: z.string(),
428
- predicateBlobBase64: z.string().min(1),
429
- }),
430
- z.object({
431
- kind: z.literal('oz_builtin'),
432
- primitive: z.object({
433
- primitive: z.enum(['spending_limit', 'simple_threshold', 'weighted_threshold']),
434
- params: z.record(z.unknown()),
435
- }),
436
- instanceAddress: z.string(),
437
- }),
438
- ]))
269
+ .array(z.object({
270
+ kind: z.literal('interpreter'),
271
+ interpreterAddress: z.string(),
272
+ predicateBlobBase64: z.string().min(1),
273
+ }))
439
274
  .max(MAX_POLICIES_PER_RULE),
440
275
  })
441
276
  .passthrough()
@@ -445,18 +280,23 @@ const MandateSpecSchemaForRule = z
445
280
  // existing four.
446
281
  /** Pinned interpreter address (testnet).
447
282
  * Single source for the MCP layer; do not embed elsewhere. */
448
- export const PINNED_INTERPRETER_TESTNET_ADDRESS = 'CDR4NLV22STCXFGZPNKDQTEANWLF7LZ6AJLY6B7CLJXKHDZGYJWIOKGP';
449
- /** Pinned interpreter address (mainnet). Mainnet
450
- * has now been deployed (2026-08-04); the mainnet interpreter IS the binary
451
- * that was exercised on testnet (same wasm sha256, see
452
- * PINNED_INTERPRETER_WASM_SHA256). The address differs because instance
453
- * ids are network-scoped. UNAUDITED at the time of writing. */
454
- export const PINNED_INTERPRETER_MAINNET_ADDRESS = 'CALZAMUPREIRY4TULBEXIK77AUTOEJG63XLCPUWEHHQDOVK6ZVVS7VQ2';
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.
289
+ *
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';
455
295
  /** Pinned interpreter wasm sha256 (hex). */
456
- export const PINNED_INTERPRETER_WASM_SHA256 = '6e6c13d93e197aa380303a42cd120f5ddb080dd36ef2a343ee1dbd04ca52a443';
296
+ export const PINNED_INTERPRETER_WASM_SHA256 = 'a2b36e8ac5a61caf3757af26aa79e83f2995b451099f44772383806a55fe3414';
457
297
  /** The grammar version the interpreter enforces (matches SELF_VERSION in
458
298
  * contracts/policy-interpreter/src/version.rs). */
459
- export const PINNED_INTERPRETER_GRAMMAR_VERSION = 1;
299
+ export const PINNED_INTERPRETER_GRAMMAR_VERSION = 3;
460
300
  /** Default Soroban RPC for the install / revoke / info tools. The recorder
461
301
  * keeps its own copy in record/rpc.ts because it hands back a fetcher rather
462
302
  * than a Server; the two are deliberately different surfaces, so this is
@@ -479,9 +319,6 @@ export const RPC_URL_BY_NETWORK = {
479
319
  testnet: TESTNET_RPC_URL,
480
320
  mainnet: MAINNET_RPC_URL,
481
321
  };
482
- /** Soroban `valid_until` window (ledgers) added to the latest ledger when
483
- * building the auth entry. ~25 minutes at 5s/ledger. */
484
- export const DEFAULT_AUTH_VALID_UNTIL_LEDGERS = 300;
485
322
  /** Stellar network passphrases. Pinned here so the XDR envelope uses the
486
323
  * matching passphrase when the wallet signs (a mismatch yields invalid
487
324
  * hashes). */
@@ -518,7 +355,7 @@ export const InstallPolicyInputSchema = z
518
355
  * pin and RPC pin do not move by themselves). */
519
356
  network: NetworkSchema.optional(),
520
357
  /** The proposed rule draft. Mirrors the core `ContextRuleDraft` shape. */
521
- rule: MandateSpecSchemaForRule,
358
+ rule: ContextRuleDraftSchema,
522
359
  /** Per-rule install nonce; 1 for a fresh install. */
523
360
  installNonce: z.number().int().positive(),
524
361
  /** Optional RPC URL override. Defaults to the pinned RPC for the
@@ -25,9 +25,7 @@ export type CustodyMode = 'enforce' | 'log_only';
25
25
  /** What a backend can express. A construct needing a false flag is flagged
26
26
  * `uncovered` by the adapter, never silently dropped. */
27
27
  export interface CustodyCapabilities {
28
- supportsOraclePrice: boolean;
29
28
  supportsSpendWindow: boolean;
30
- supportsInvocationCount: boolean;
31
29
  supportsTimeExpiry: boolean;
32
30
  supportsThreshold: boolean;
33
31
  supportsGeneralPredicate: boolean;
@@ -0,0 +1,26 @@
1
+ import type { PredicateNode, ScVal } from '../types.ts';
2
+ import type { EvalContext } from './evaluate.ts';
3
+ export interface DenyCase {
4
+ dimension: string;
5
+ ctx: EvalContext;
6
+ /** Canonical reason the Rust interpreter returns for this dimension.
7
+ * Optional: when set, `runHarness` asserts the TS evaluator emits this
8
+ * exact string, so a future TS/Rust reason divergence fails CI. When
9
+ * absent the case is only checked on the boolean decision. */
10
+ expectedReason?: string;
11
+ }
12
+ export interface GeneratedCases {
13
+ permit: EvalContext;
14
+ denies: DenyCase[];
15
+ }
16
+ /** Build deterministic model-evaluated alternatives without mutating the intended call.
17
+ *
18
+ * @param predicate - the synthesized predicate
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. */
23
+ export declare function generateCases(predicate: PredicateNode, permitCtx: EvalContext, dimensions?: string[]): GeneratedCases;
24
+ /** Deep-copy an ScVal so a mutation cannot alias the recorded call.
25
+ * Exported so the permit-context builder shares this one implementation. */
26
+ export declare function cloneScVal(value: ScVal, depth?: number): ScVal;