@crediolabs/policy-synth 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/adapters/interpreter/adapter.d.ts +19 -25
- package/dist/adapters/interpreter/adapter.js +54 -487
- package/dist/adapters/interpreter/index.d.ts +1 -1
- package/dist/adapters/interpreter/index.js +1 -1
- package/dist/adapters/oz/adapter.js +1 -15
- package/dist/errors.d.ts +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -4
- package/dist/install/build-add-context-rule.d.ts +3 -19
- package/dist/install/build-add-context-rule.js +3 -98
- package/dist/install/build-install-policy.d.ts +4 -53
- package/dist/install/build-install-policy.js +44 -123
- package/dist/install/index.d.ts +0 -2
- package/dist/install/index.js +0 -7
- package/dist/ir/index.d.ts +1 -1
- package/dist/ir/types.d.ts +7 -76
- package/dist/ir/types.js +0 -2
- package/dist/predicate/decode.d.ts +1 -1
- package/dist/predicate/decode.js +2 -70
- package/dist/predicate/encode.js +45 -229
- package/dist/predicate/from-json.js +1 -42
- package/dist/record/decode.js +3 -8
- package/dist/review-card/builder.d.ts +4 -3
- package/dist/review-card/builder.js +18 -162
- package/dist/review-card/cross-check.js +2 -105
- package/dist/review-card/render-leaf.d.ts +4 -0
- package/dist/review-card/render-leaf.js +47 -0
- package/dist/run/index.d.ts +36 -139
- package/dist/run/index.js +125 -437
- package/dist/run/schemas.d.ts +168 -1929
- package/dist/run/schemas.js +40 -269
- package/dist/seams/types.d.ts +0 -2
- package/dist/simulate/deny-cases.d.ts +26 -0
- package/dist/simulate/deny-cases.js +282 -0
- package/dist/simulate/evaluate.d.ts +18 -0
- package/dist/simulate/evaluate.js +271 -0
- package/dist/simulate/index.d.ts +4 -0
- package/dist/simulate/index.js +8 -0
- package/dist/synth/compose-from-recording.d.ts +24 -51
- package/dist/synth/compose-from-recording.js +120 -282
- package/dist/synth/deny-cases.d.ts +5 -7
- package/dist/synth/deny-cases.js +23 -373
- package/dist/synth/evaluate.d.ts +0 -21
- package/dist/synth/evaluate.js +56 -336
- package/dist/synth/index.d.ts +0 -5
- package/dist/synth/index.js +0 -5
- package/dist/synth/permit-context.d.ts +2 -9
- package/dist/synth/permit-context.js +1 -82
- package/dist/synth/synthesize-from-recording.d.ts +1 -29
- package/dist/synth/synthesize-from-recording.js +41 -356
- package/dist/types.d.ts +8 -60
- package/dist/types.js +5 -2
- package/dist/verify/simulate.d.ts +1 -14
- package/dist/verify/simulate.js +2 -88
- package/dist/verify/verify.d.ts +1 -7
- package/dist/verify/verify.js +0 -8
- package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
- package/dist-cjs/adapters/interpreter/adapter.js +56 -489
- package/dist-cjs/adapters/interpreter/index.d.ts +1 -1
- package/dist-cjs/adapters/interpreter/index.js +1 -2
- package/dist-cjs/adapters/oz/adapter.js +1 -15
- package/dist-cjs/errors.d.ts +1 -1
- package/dist-cjs/index.d.ts +1 -4
- package/dist-cjs/index.js +1 -4
- package/dist-cjs/install/build-add-context-rule.d.ts +3 -19
- package/dist-cjs/install/build-add-context-rule.js +1 -97
- package/dist-cjs/install/build-install-policy.d.ts +4 -53
- package/dist-cjs/install/build-install-policy.js +44 -122
- package/dist-cjs/install/index.d.ts +0 -2
- package/dist-cjs/install/index.js +2 -23
- package/dist-cjs/ir/index.d.ts +1 -1
- package/dist-cjs/ir/types.d.ts +7 -76
- package/dist-cjs/ir/types.js +0 -2
- package/dist-cjs/predicate/decode.d.ts +1 -1
- package/dist-cjs/predicate/decode.js +2 -70
- package/dist-cjs/predicate/encode.js +45 -229
- package/dist-cjs/predicate/from-json.js +1 -42
- package/dist-cjs/record/decode.js +3 -8
- package/dist-cjs/review-card/builder.d.ts +4 -3
- package/dist-cjs/review-card/builder.js +23 -167
- package/dist-cjs/review-card/cross-check.js +7 -110
- package/dist-cjs/review-card/render-leaf.d.ts +4 -0
- package/dist-cjs/review-card/render-leaf.js +52 -0
- package/dist-cjs/run/index.d.ts +36 -139
- package/dist-cjs/run/index.js +125 -444
- package/dist-cjs/run/schemas.d.ts +168 -1929
- package/dist-cjs/run/schemas.js +41 -270
- package/dist-cjs/seams/types.d.ts +0 -2
- package/dist-cjs/simulate/deny-cases.d.ts +26 -0
- package/dist-cjs/simulate/deny-cases.js +286 -0
- package/dist-cjs/simulate/evaluate.d.ts +18 -0
- package/dist-cjs/simulate/evaluate.js +274 -0
- package/dist-cjs/simulate/index.d.ts +4 -0
- package/dist-cjs/simulate/index.js +13 -0
- package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
- package/dist-cjs/synth/compose-from-recording.js +120 -282
- package/dist-cjs/synth/deny-cases.d.ts +5 -7
- package/dist-cjs/synth/deny-cases.js +23 -374
- package/dist-cjs/synth/evaluate.d.ts +0 -21
- package/dist-cjs/synth/evaluate.js +57 -337
- package/dist-cjs/synth/index.d.ts +0 -5
- package/dist-cjs/synth/index.js +1 -11
- package/dist-cjs/synth/permit-context.d.ts +2 -9
- package/dist-cjs/synth/permit-context.js +1 -82
- package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
- package/dist-cjs/synth/synthesize-from-recording.js +39 -354
- package/dist-cjs/types.d.ts +8 -60
- package/dist-cjs/types.js +6 -3
- package/dist-cjs/verify/simulate.d.ts +1 -14
- package/dist-cjs/verify/simulate.js +2 -88
- package/dist-cjs/verify/verify.d.ts +1 -7
- package/dist-cjs/verify/verify.js +0 -8
- package/package.json +4 -4
- package/src/adapters/interpreter/adapter.ts +76 -572
- package/src/errors.ts +0 -19
- package/src/index.ts +1 -4
- package/src/install/build-add-context-rule.ts +5 -139
- package/src/install/build-install-policy.ts +93 -214
- package/src/install/index.ts +0 -34
- package/src/predicate/decode.ts +2 -70
- package/src/predicate/encode.ts +49 -261
- package/src/predicate/from-json.ts +1 -43
- package/src/record/decode.ts +3 -8
- package/src/review-card/builder.ts +19 -168
- package/src/review-card/cross-check.ts +3 -105
- package/src/review-card/render-leaf.ts +48 -0
- package/src/run/index.ts +144 -572
- package/src/run/schemas.ts +42 -291
- package/src/simulate/deny-cases.ts +334 -0
- package/src/simulate/evaluate.ts +284 -0
- package/src/simulate/index.ts +11 -0
- package/src/synth/compose-from-recording.ts +148 -347
- package/src/synth/index.ts +0 -13
- package/src/synth/synthesize-from-recording.ts +43 -456
- package/src/types.ts +13 -49
- package/dist/install/authority-overlap.d.ts +0 -134
- package/dist/install/authority-overlap.js +0 -0
- package/dist/install/build-install-predicate.d.ts +0 -96
- package/dist/install/build-install-predicate.js +0 -444
- package/dist/install/build-merge-policy.d.ts +0 -70
- package/dist/install/build-merge-policy.js +0 -130
- package/dist/install/plan-merge-policy.d.ts +0 -49
- package/dist/install/plan-merge-policy.js +0 -86
- package/dist/install/read-account-rules.d.ts +0 -100
- package/dist/install/read-account-rules.js +0 -283
- package/dist-cjs/install/authority-overlap.d.ts +0 -134
- package/dist-cjs/install/authority-overlap.js +0 -0
- package/dist-cjs/install/build-install-predicate.d.ts +0 -96
- package/dist-cjs/install/build-install-predicate.js +0 -479
- package/dist-cjs/install/build-merge-policy.d.ts +0 -70
- package/dist-cjs/install/build-merge-policy.js +0 -134
- package/dist-cjs/install/plan-merge-policy.d.ts +0 -49
- package/dist-cjs/install/plan-merge-policy.js +0 -90
- package/dist-cjs/install/read-account-rules.d.ts +0 -100
- package/dist-cjs/install/read-account-rules.js +0 -296
- package/src/adapters/interpreter/index.ts +0 -8
- package/src/adapters/oz/adapter.ts +0 -376
- package/src/adapters/oz/index.ts +0 -9
- package/src/codegen/compile-gate.ts +0 -167
- package/src/codegen/index.ts +0 -17
- package/src/codegen/template.ts +0 -165
- package/src/install/authority-overlap.ts +0 -0
- package/src/install/build-merge-policy.ts +0 -219
- package/src/install/plan-merge-policy.ts +0 -133
- package/src/install/read-account-rules.ts +0 -376
- package/src/ir/index.ts +0 -13
- package/src/ir/types.ts +0 -132
- package/src/mandate/index.ts +0 -4
- package/src/mandate/to-ir.ts +0 -71
- package/src/mandate/types.ts +0 -21
- package/src/seams/index.ts +0 -11
- package/src/seams/types.ts +0 -81
- package/src/synth/deny-cases.ts +0 -663
- package/src/synth/evaluate.ts +0 -613
- package/src/synth/harness.ts +0 -68
- package/src/synth/minimize.ts +0 -48
- package/src/synth/permit-context.ts +0 -136
- package/src/synth/predicate-literals.ts +0 -27
- package/src/synth/synthesize-from-mandate.ts +0 -82
- package/src/verify/envelope.ts +0 -28
- package/src/verify/index.ts +0 -5
- package/src/verify/simulate.ts +0 -311
- package/src/verify/verify.ts +0 -243
package/src/run/schemas.ts
CHANGED
|
@@ -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
|
-
//
|
|
12
|
-
//
|
|
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
|
|
@@ -113,62 +113,13 @@ export const RecordedTransactionSchema = z
|
|
|
113
113
|
})
|
|
114
114
|
.passthrough()
|
|
115
115
|
|
|
116
|
-
/** MandateSpec mirrors the core MandateSpec. The deterministic Mandate
|
|
117
|
-
* front-end needs no parseConfidence; the tool adapter injects the full
|
|
118
|
-
* confidence after synthesis so the orchestrator can compare. */
|
|
119
|
-
export const MandateSpecSchema = z
|
|
120
|
-
.object({
|
|
121
|
-
chain: z.literal('stellar'),
|
|
122
|
-
contract: z.string(),
|
|
123
|
-
method: z.string().optional(),
|
|
124
|
-
spendingLimit: z
|
|
125
|
-
.object({
|
|
126
|
-
token: z.string(),
|
|
127
|
-
limit: z.string().regex(/^[0-9]+$/),
|
|
128
|
-
windowSeconds: z.number().int().positive(),
|
|
129
|
-
})
|
|
130
|
-
.optional(),
|
|
131
|
-
// A threshold of 0 means "0 approvals", which is not a real M-of-N gate.
|
|
132
|
-
approvalThreshold: z.number().int().positive().optional(),
|
|
133
|
-
recipients: z.array(z.string()).optional(),
|
|
134
|
-
expiry: z
|
|
135
|
-
.object({
|
|
136
|
-
validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
|
|
137
|
-
validUntilUnixSeconds: z.number().int().positive().optional(),
|
|
138
|
-
})
|
|
139
|
-
.optional(),
|
|
140
|
-
})
|
|
141
|
-
.passthrough()
|
|
142
|
-
// TS-F4/F6: a `recipients` allowlist is only meaningful against a SEP-41
|
|
143
|
-
// method whose arg 1 IS the recipient (SAC/SEP-41 `transfer(from, to,
|
|
144
|
-
// amount)` and SEP-41 `mint(to, amount)`). Without this gate the
|
|
145
|
-
// `to-ir.ts` lowering pins the allowlist to `RECIPIENT_ARG_INDEX = 1`
|
|
146
|
-
// for any contract+method, which would let a non-SEP-41 method's
|
|
147
|
-
// arg[1] (e.g. an amount, an op type, an arbitrary address payload)
|
|
148
|
-
// be silently constrained as if it were a recipient. Refusing
|
|
149
|
-
// non-SEP-41 methods at the boundary is the fail-closed shape.
|
|
150
|
-
.refine(
|
|
151
|
-
(v) =>
|
|
152
|
-
v.recipients === undefined ||
|
|
153
|
-
v.recipients.length === 0 ||
|
|
154
|
-
v.method === 'transfer' ||
|
|
155
|
-
v.method === 'mint',
|
|
156
|
-
{
|
|
157
|
-
message:
|
|
158
|
-
'recipients is only valid when method is a SEP-41 method (transfer or mint); other methods do not have a recipient at arg[1]',
|
|
159
|
-
}
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
/** ComposeUserResponses mirrors the core. */
|
|
163
116
|
export const ComposeUserResponsesSchema = z
|
|
164
117
|
.object({
|
|
165
|
-
windowSeconds: z.number().int().positive().optional(),
|
|
166
118
|
validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
|
|
167
119
|
limitAmount: z
|
|
168
120
|
.string()
|
|
169
121
|
.regex(/^[0-9]+$/)
|
|
170
122
|
.optional(),
|
|
171
|
-
invocationLimit: z.number().int().positive().optional(),
|
|
172
123
|
// Swap recipient allowlist (SoroSwap call_arg[3]). Each entry must be a
|
|
173
124
|
// Stellar address (G... wallet or C... contract); supplying it REPLACES the
|
|
174
125
|
// default pin to the recorded recipient. Validated with the shared StrKey
|
|
@@ -176,37 +127,11 @@ export const ComposeUserResponsesSchema = z
|
|
|
176
127
|
swapRecipientAllowlist: z
|
|
177
128
|
.array(z.string().refine(isStellarAddress, 'must be a Stellar address (G... or C...)'))
|
|
178
129
|
.optional(),
|
|
179
|
-
// Per-asset oracle-price bound, one entry per asset. Reaches
|
|
180
|
-
// `compose-from-recording` and emits an `oracle_price` compare. Declared
|
|
181
|
-
// here because the object is `.passthrough()`: the field already worked
|
|
182
|
-
// undeclared, which left the accepted surface wider than the documented
|
|
183
|
-
// one. `decimals` is REQUIRED - oracle prices normalise to 9 dp and a
|
|
184
|
-
// threshold silently assumed to share that basis is what let a raw 14-dp
|
|
185
|
-
// bound permit everything.
|
|
186
|
-
oraclePriceBound: z
|
|
187
|
-
.array(
|
|
188
|
-
z.object({
|
|
189
|
-
asset: z.string().refine(isStellarAddress, 'must be a Stellar address (G... or C...)'),
|
|
190
|
-
operator: z.enum(['eq', 'lt', 'lte', 'gt', 'gte']),
|
|
191
|
-
value: z.string().regex(/^[0-9]+$/),
|
|
192
|
-
decimals: z.number().int().min(0).max(U32_MAX),
|
|
193
|
-
})
|
|
194
|
-
)
|
|
195
|
-
.optional(),
|
|
196
130
|
})
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
network: NetworkSchema,
|
|
202
|
-
instances: z.object({
|
|
203
|
-
spending_limit: z.string(),
|
|
204
|
-
simple_threshold: z.string(),
|
|
205
|
-
weighted_threshold: z.string(),
|
|
206
|
-
}),
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
// ===== record_transaction =====
|
|
131
|
+
// Strict, not passthrough: a response key the synthesiser no longer reads
|
|
132
|
+
// (or a typo) would otherwise be accepted in silence, and the caller would
|
|
133
|
+
// believe they had answered a question that is still open.
|
|
134
|
+
.strict()
|
|
210
135
|
|
|
211
136
|
export const RecordTransactionInputSchema = z
|
|
212
137
|
.object({
|
|
@@ -223,48 +148,18 @@ export const RecordTransactionInputSchema = z
|
|
|
223
148
|
})
|
|
224
149
|
export type RecordTransactionInput = z.infer<typeof RecordTransactionInputSchema>
|
|
225
150
|
|
|
226
|
-
// ===== synthesize_policy =====
|
|
227
|
-
//
|
|
228
|
-
// Discriminated union on `source` exposes BOTH front-ends through ONE tool.
|
|
229
|
-
// - `source: 'mandate'` -> calls synthesizeFromMandate
|
|
230
|
-
// - `source: 'recording'` -> calls synthesizeFromRecording
|
|
231
|
-
|
|
232
|
-
export const SynthesizePolicyMandateInputSchema = z.object({
|
|
233
|
-
source: z.literal('mandate'),
|
|
234
|
-
mandate: MandateSpecSchema,
|
|
235
|
-
ozConfig: OzAdapterConfigSchema.optional(),
|
|
236
|
-
// --explain opt-in. When true, the orchestrator attaches the
|
|
237
|
-
// in-memory predicate tree (null for the mandate path) + a minimal
|
|
238
|
-
// honest SimulationResult to the success envelope. Absent or false
|
|
239
|
-
// -> the success envelope is unchanged (byte-identical to today).
|
|
240
|
-
explain: z.boolean().optional(),
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
/** Interpreter opt-in for the recording path. Present -> constraints OZ cannot
|
|
244
|
-
* express (per-method scoping, invocation-count windows, oracle bounds, exact
|
|
245
|
-
* hop paths) lower to a real interpreter predicate document instead of being
|
|
246
|
-
* surfaced as warnings. The core deep-validates `smartAccountAddress` (a C...
|
|
247
|
-
* contract, not the recording's G... source) and the tighten-only oracle
|
|
248
|
-
* bounds; the schema stays light so the core owns the friendly ToolErrors. */
|
|
249
151
|
export const InterpreterOptionsSchema = z.object({
|
|
250
152
|
smartAccountAddress: z.string(),
|
|
251
153
|
installNonce: z.number().int().positive().optional(),
|
|
252
|
-
oracleParams: z
|
|
253
|
-
.object({
|
|
254
|
-
maxStalenessSeconds: z.number().int().positive().optional(),
|
|
255
|
-
maxDeviationBps: z.number().int().positive().optional(),
|
|
256
|
-
})
|
|
257
|
-
.optional(),
|
|
258
154
|
})
|
|
259
155
|
|
|
260
|
-
export const
|
|
156
|
+
export const SynthesizePolicyInputSchema = z.object({
|
|
261
157
|
source: z.literal('recording'),
|
|
262
158
|
recordedTx: RecordedTransactionSchema,
|
|
263
159
|
network: NetworkSchema,
|
|
264
160
|
userResponses: ComposeUserResponsesSchema.optional(),
|
|
265
161
|
confidenceOverride: z.object({ threshold: z.number().min(0).max(1) }).optional(),
|
|
266
162
|
interpreter: InterpreterOptionsSchema.optional(),
|
|
267
|
-
ozConfig: OzAdapterConfigSchema.optional(),
|
|
268
163
|
// --explain opt-in. When true, the orchestrator attaches the
|
|
269
164
|
// in-memory PredicateNode + the corresponding SimulationResult
|
|
270
165
|
// (real one from the self-verify pipeline when the interpreter is
|
|
@@ -276,10 +171,6 @@ export const SynthesizePolicyRecordingInputSchema = z.object({
|
|
|
276
171
|
explain: z.boolean().optional(),
|
|
277
172
|
})
|
|
278
173
|
|
|
279
|
-
export const SynthesizePolicyInputSchema = z.discriminatedUnion('source', [
|
|
280
|
-
SynthesizePolicyMandateInputSchema,
|
|
281
|
-
SynthesizePolicyRecordingInputSchema,
|
|
282
|
-
])
|
|
283
174
|
export type SynthesizePolicyInput = z.infer<typeof SynthesizePolicyInputSchema>
|
|
284
175
|
|
|
285
176
|
// ===== PredicateNode / PredicateLeaf =====
|
|
@@ -310,41 +201,6 @@ export const PredicateLeafSchema: z.ZodType<unknown> = z.lazy(() =>
|
|
|
310
201
|
element: z.number().int().nonnegative(),
|
|
311
202
|
field: z.string(),
|
|
312
203
|
}),
|
|
313
|
-
// num/den are i128 on chain; JSON numbers lose precision past 2^53,
|
|
314
|
-
// so the wire is a decimal string. The contract refuses `den == 0` and
|
|
315
|
-
// `num <= 0` / `den <= 0` at install (types.ts:162-163 + the install
|
|
316
|
-
// gate); mirroring that bound here closes the gap where simulate /
|
|
317
|
-
// verify green-light a policy the install gate will reject. The
|
|
318
|
-
// positive-only regex is the cheapest unambiguous check.
|
|
319
|
-
z.object({
|
|
320
|
-
kind: z.literal('call_arg_scaled'),
|
|
321
|
-
index: z.number().int().nonnegative(),
|
|
322
|
-
num: z.string().regex(/^[1-9][0-9]*$/),
|
|
323
|
-
den: z.string().regex(/^[1-9][0-9]*$/),
|
|
324
|
-
}),
|
|
325
|
-
z.object({ kind: z.literal('amount'), token: z.string() }),
|
|
326
|
-
z.object({
|
|
327
|
-
kind: z.literal('window_spent'),
|
|
328
|
-
token: z.string(),
|
|
329
|
-
windowSeconds: z.number().int().positive(),
|
|
330
|
-
}),
|
|
331
|
-
z.object({ kind: z.literal('now') }),
|
|
332
|
-
// `valid_until` is NOT in the public predicate grammar: the encoder +
|
|
333
|
-
// decoder already throw on it, so accepting it here would let a
|
|
334
|
-
// hand-crafted payload through the schema and only surface a 200+
|
|
335
|
-
// character internal-commentary error at encode time. Drop it - the
|
|
336
|
-
// policy's expiry is carried at the install layer (MandateSpec +
|
|
337
|
-
// validUntilLedger) instead.
|
|
338
|
-
z.object({
|
|
339
|
-
kind: z.literal('invocation_count_in_window'),
|
|
340
|
-
windowSecs: z.number().int().positive(),
|
|
341
|
-
}),
|
|
342
|
-
z.object({ kind: z.literal('oracle_price'), asset: z.string() }),
|
|
343
|
-
z.object({
|
|
344
|
-
kind: z.literal('oracle_threshold'),
|
|
345
|
-
value: z.string().regex(/^-?[0-9]+$/),
|
|
346
|
-
decimals: z.number().int().nonnegative(),
|
|
347
|
-
}),
|
|
348
204
|
z.object({ kind: z.literal('literal_address'), value: z.string() }),
|
|
349
205
|
z.object({ kind: z.literal('literal_i128'), value: z.string().regex(/^-?[0-9]+$/) }),
|
|
350
206
|
z.object({ kind: z.literal('literal_symbol'), value: z.string() }),
|
|
@@ -356,39 +212,23 @@ export const PredicateLeafSchema: z.ZodType<unknown> = z.lazy(() =>
|
|
|
356
212
|
kind: z.literal('literal_u32'),
|
|
357
213
|
value: z.number().int().nonnegative().max(U32_MAX),
|
|
358
214
|
}),
|
|
359
|
-
z.object({ kind: z.literal('literal_u64'), value: z.string().regex(/^[0-9]+$/) }),
|
|
360
|
-
// `literal_bytes` is hex on chain. `Buffer.from(value, 'hex')` silently
|
|
361
|
-
// drops non-hex chars AND yields an empty buffer when the whole input
|
|
362
|
-
// is non-hex - so a user passing 'zzzz' would get a predicate that
|
|
363
|
-
// compares against empty bytes instead of being rejected. The
|
|
364
|
-
// strict even-length hex regex closes that gap at the boundary.
|
|
365
|
-
z.object({
|
|
366
|
-
kind: z.literal('literal_bytes'),
|
|
367
|
-
value: z
|
|
368
|
-
.string()
|
|
369
|
-
.regex(/^[0-9a-fA-F]*$/)
|
|
370
|
-
.refine((v) => v.length % 2 === 0, 'must be even-length hex'),
|
|
371
|
-
}),
|
|
372
215
|
// elements are themselves PredicateLeaf; lazy to break the cycle.
|
|
373
216
|
z.object({ kind: z.literal('literal_vec'), elements: z.array(PredicateLeafSchema) }),
|
|
374
217
|
])
|
|
375
218
|
)
|
|
376
219
|
|
|
377
220
|
/** PredicateNode mirrors the core `PredicateNode` union. Recursive through
|
|
378
|
-
* `and
|
|
379
|
-
* type-safe. */
|
|
221
|
+
* `and`; the lazy + annotation pattern keeps the recursion type-safe. */
|
|
380
222
|
export const PredicateNodeSchema: z.ZodType<unknown> = z.lazy(() =>
|
|
381
223
|
z.union([
|
|
382
224
|
z.object({ op: z.literal('and'), children: z.array(PredicateNodeSchema) }),
|
|
383
|
-
z.object({ op: z.literal('or'), children: z.array(PredicateNodeSchema) }),
|
|
384
|
-
z.object({ op: z.literal('not'), child: PredicateNodeSchema }),
|
|
385
225
|
z.object({
|
|
386
226
|
op: z.literal('eq'),
|
|
387
227
|
left: PredicateLeafSchema,
|
|
388
228
|
right: PredicateLeafSchema,
|
|
389
229
|
}),
|
|
390
230
|
z.object({
|
|
391
|
-
op: z.
|
|
231
|
+
op: z.literal('lte'),
|
|
392
232
|
left: PredicateLeafSchema,
|
|
393
233
|
right: PredicateLeafSchema,
|
|
394
234
|
}),
|
|
@@ -402,42 +242,18 @@ export const PredicateNodeSchema: z.ZodType<unknown> = z.lazy(() =>
|
|
|
402
242
|
|
|
403
243
|
// ===== simulate_policy / verify_policy =====
|
|
404
244
|
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
// payload cannot leak a u32 overflow into the engine's permit context.
|
|
410
|
-
|
|
411
|
-
/** Oracle price fixture - mirror of the entry type in `SimulateOptions` /
|
|
412
|
-
* `VerifyOptions`. Discriminated by presence of `error` vs `price`. */
|
|
413
|
-
export const OraclePriceFixtureSchema = z.union([
|
|
414
|
-
z.object({
|
|
415
|
-
price: z.string().regex(/^[0-9]+$/),
|
|
416
|
-
timestampSeconds: z.number().int().nonnegative(),
|
|
417
|
-
}),
|
|
418
|
-
z.object({
|
|
419
|
-
error: z.enum(['stale', 'missing', 'deviation', 'paused', 'decimals', 'fingerprint']),
|
|
420
|
-
}),
|
|
421
|
-
])
|
|
422
|
-
|
|
245
|
+
// Both tools evaluate the same predicate against the same recording, so they
|
|
246
|
+
// take the same input. A null predicate used to mean "OZ built-in policies
|
|
247
|
+
// only"; that backend is gone, so every policy carries a predicate and there is
|
|
248
|
+
// nothing to simulate without one.
|
|
423
249
|
export const SimulatePolicyInputSchema = z.object({
|
|
424
|
-
|
|
425
|
-
// does not. The asymmetry is mirrored at the schema boundary.
|
|
426
|
-
predicate: PredicateNodeSchema.nullable(),
|
|
250
|
+
predicate: PredicateNodeSchema,
|
|
427
251
|
permitTx: RecordedTransactionSchema,
|
|
428
252
|
validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
|
|
429
|
-
oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
|
|
430
253
|
})
|
|
431
254
|
export type SimulatePolicyInput = z.infer<typeof SimulatePolicyInputSchema>
|
|
432
255
|
|
|
433
|
-
export const VerifyPolicyInputSchema =
|
|
434
|
-
// verifyPolicy requires a non-null predicate; the engine refuses
|
|
435
|
-
// `null` outright. The schema mirrors that contract.
|
|
436
|
-
predicate: PredicateNodeSchema,
|
|
437
|
-
permitTx: RecordedTransactionSchema,
|
|
438
|
-
validUntilLedger: z.number().int().positive().max(U32_MAX).optional(),
|
|
439
|
-
oraclePricesByAsset: z.record(z.string(), OraclePriceFixtureSchema).optional(),
|
|
440
|
-
})
|
|
256
|
+
export const VerifyPolicyInputSchema = SimulatePolicyInputSchema
|
|
441
257
|
export type VerifyPolicyInput = z.infer<typeof VerifyPolicyInputSchema>
|
|
442
258
|
|
|
443
259
|
// ===== install_policy / revoke_policy / get_interpreter_info =====
|
|
@@ -452,7 +268,7 @@ export type VerifyPolicyInput = z.infer<typeof VerifyPolicyInputSchema>
|
|
|
452
268
|
// optional `grammar_version()` RPC call to check whether the deployed contract
|
|
453
269
|
// matches the pin. A mismatch is worth MORE than a fabricated audit field.
|
|
454
270
|
//
|
|
455
|
-
// All three input schemas live BELOW the
|
|
271
|
+
// All three input schemas live BELOW the ContextRuleDraftSchema declaration
|
|
456
272
|
// so the const reference there is not in the temporal dead zone (no JS hoisting
|
|
457
273
|
// for `const`).
|
|
458
274
|
|
|
@@ -465,7 +281,7 @@ export type VerifyPolicyInput = z.infer<typeof VerifyPolicyInputSchema>
|
|
|
465
281
|
const MAX_SIGNERS_PER_RULE = 15
|
|
466
282
|
const MAX_POLICIES_PER_RULE = 5
|
|
467
283
|
|
|
468
|
-
const
|
|
284
|
+
const ContextRuleDraftSchema = z
|
|
469
285
|
.object({
|
|
470
286
|
contextRuleType: z.discriminatedUnion('kind', [
|
|
471
287
|
z.object({ kind: z.literal('default') }),
|
|
@@ -488,21 +304,11 @@ const MandateSpecSchemaForRule = z
|
|
|
488
304
|
.max(MAX_SIGNERS_PER_RULE),
|
|
489
305
|
policies: z
|
|
490
306
|
.array(
|
|
491
|
-
z.
|
|
492
|
-
z.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}),
|
|
497
|
-
z.object({
|
|
498
|
-
kind: z.literal('oz_builtin'),
|
|
499
|
-
primitive: z.object({
|
|
500
|
-
primitive: z.enum(['spending_limit', 'simple_threshold', 'weighted_threshold']),
|
|
501
|
-
params: z.record(z.unknown()),
|
|
502
|
-
}),
|
|
503
|
-
instanceAddress: z.string(),
|
|
504
|
-
}),
|
|
505
|
-
])
|
|
307
|
+
z.object({
|
|
308
|
+
kind: z.literal('interpreter'),
|
|
309
|
+
interpreterAddress: z.string(),
|
|
310
|
+
predicateBlobBase64: z.string().min(1),
|
|
311
|
+
})
|
|
506
312
|
)
|
|
507
313
|
.max(MAX_POLICIES_PER_RULE),
|
|
508
314
|
})
|
|
@@ -519,34 +325,28 @@ const MandateSpecSchemaForRule = z
|
|
|
519
325
|
/** Pinned interpreter address (testnet).
|
|
520
326
|
* Single source for the MCP layer; do not embed elsewhere. */
|
|
521
327
|
export const PINNED_INTERPRETER_TESTNET_ADDRESS =
|
|
522
|
-
'
|
|
328
|
+
'CCL336TCK2Y5OFNRCMN2M3HVPBCEX4PW5H6EQ5VW5NPMXOCP4ESB5XR4'
|
|
523
329
|
|
|
524
|
-
/** Pinned interpreter address (mainnet),
|
|
525
|
-
*
|
|
330
|
+
/** Pinned interpreter address (mainnet), redeployed 2026-08-22 from a reproducible build. The mainnet
|
|
331
|
+
* interpreter IS the binary exercised on testnet - both instances were created
|
|
332
|
+
* from the same uploaded wasm hash (see PINNED_INTERPRETER_WASM_SHA256), and
|
|
333
|
+
* both were read back with `grammar_version()` returning 3. The address differs
|
|
334
|
+
* because instance ids are network-scoped. UNAUDITED at the time of writing.
|
|
335
|
+
*
|
|
336
|
+
* These four constants move together or not at all. The grammar version and
|
|
337
|
+
* wasm hash are single values covering BOTH networks, so re-pinning one network
|
|
338
|
+
* alone would have the builder emit a version the other network refuses - with
|
|
339
|
+
* a green test run, since `grammar-version-parity.test.ts` would then pass. */
|
|
526
340
|
export const PINNED_INTERPRETER_MAINNET_ADDRESS =
|
|
527
|
-
'
|
|
341
|
+
'CBZXLSTQUITBFZHQH6XRXF3XIVRQR4RHRI64Q5WELS5KGY3ZKJPFWDPF'
|
|
528
342
|
|
|
529
|
-
/** Pinned interpreter wasm sha256 (hex)
|
|
530
|
-
*
|
|
531
|
-
* The two networks no longer run the same binary. Testnet carries the
|
|
532
|
-
* selector-leaf minimum and the signer-set cap; mainnet predates both. Read
|
|
533
|
-
* the hash through `PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK` rather than
|
|
534
|
-
* this constant unless mainnet is specifically what is meant, or
|
|
535
|
-
* `get_interpreter_info` will report a hash the queried network does not
|
|
536
|
-
* run. */
|
|
343
|
+
/** Pinned interpreter wasm sha256 (hex). */
|
|
537
344
|
export const PINNED_INTERPRETER_WASM_SHA256 =
|
|
538
|
-
'
|
|
539
|
-
|
|
540
|
-
/** Pinned interpreter wasm sha256 (hex), testnet. Byte-identical to the
|
|
541
|
-
* artifact built from `contracts/policy-interpreter` at the commit that
|
|
542
|
-
* introduced errors 216 and 217, verified by fetching the deployed wasm back
|
|
543
|
-
* off chain. */
|
|
544
|
-
export const PINNED_INTERPRETER_WASM_SHA256_TESTNET =
|
|
545
|
-
'a4d58bc88fd82bbb8e223941ba5889db919717fae92ced13f6f55bfe583b8e22'
|
|
345
|
+
'a2b36e8ac5a61caf3757af26aa79e83f2995b451099f44772383806a55fe3414'
|
|
546
346
|
|
|
547
347
|
/** The grammar version the interpreter enforces (matches SELF_VERSION in
|
|
548
348
|
* contracts/policy-interpreter/src/version.rs). */
|
|
549
|
-
export const PINNED_INTERPRETER_GRAMMAR_VERSION =
|
|
349
|
+
export const PINNED_INTERPRETER_GRAMMAR_VERSION = 3
|
|
550
350
|
|
|
551
351
|
/** Default Soroban RPC for the install / revoke / info tools. The recorder
|
|
552
352
|
* keeps its own copy in record/rpc.ts because it hands back a fetcher rather
|
|
@@ -560,27 +360,19 @@ export const TESTNET_RPC_URL = 'https://soroban-testnet.stellar.org'
|
|
|
560
360
|
* the deploy script hit during the 2026-08-04 mainnet rollout. */
|
|
561
361
|
export const MAINNET_RPC_URL = 'https://mainnet.sorobanrpc.com'
|
|
562
362
|
|
|
563
|
-
/** Pin + RPC lookup for the gate enforcement.
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
363
|
+
/** Pin + RPC lookup for the gate enforcement. The interpreters' wasm sha256
|
|
364
|
+
* is identical across both networks (the same binary was uploaded both
|
|
365
|
+
* places), so `PINNED_INTERPRETER_WASM_SHA256` stays
|
|
366
|
+
* a single constant - only the addresses and RPCs are network-scoped. */
|
|
567
367
|
export const PINNED_INTERPRETER_ADDRESS_BY_NETWORK: Record<Network, string> = {
|
|
568
368
|
testnet: PINNED_INTERPRETER_TESTNET_ADDRESS,
|
|
569
369
|
mainnet: PINNED_INTERPRETER_MAINNET_ADDRESS,
|
|
570
370
|
}
|
|
571
|
-
export const PINNED_INTERPRETER_WASM_SHA256_BY_NETWORK: Record<Network, string> = {
|
|
572
|
-
testnet: PINNED_INTERPRETER_WASM_SHA256_TESTNET,
|
|
573
|
-
mainnet: PINNED_INTERPRETER_WASM_SHA256,
|
|
574
|
-
}
|
|
575
371
|
export const RPC_URL_BY_NETWORK: Record<Network, string> = {
|
|
576
372
|
testnet: TESTNET_RPC_URL,
|
|
577
373
|
mainnet: MAINNET_RPC_URL,
|
|
578
374
|
}
|
|
579
375
|
|
|
580
|
-
/** Soroban `valid_until` window (ledgers) added to the latest ledger when
|
|
581
|
-
* building the auth entry. ~25 minutes at 5s/ledger. */
|
|
582
|
-
export const DEFAULT_AUTH_VALID_UNTIL_LEDGERS = 300
|
|
583
|
-
|
|
584
376
|
/** Stellar network passphrases. Pinned here so the XDR envelope uses the
|
|
585
377
|
* matching passphrase when the wallet signs (a mismatch yields invalid
|
|
586
378
|
* hashes). */
|
|
@@ -619,7 +411,7 @@ export const InstallPolicyInputSchema = z
|
|
|
619
411
|
* pin and RPC pin do not move by themselves). */
|
|
620
412
|
network: NetworkSchema.optional(),
|
|
621
413
|
/** The proposed rule draft. Mirrors the core `ContextRuleDraft` shape. */
|
|
622
|
-
rule:
|
|
414
|
+
rule: ContextRuleDraftSchema,
|
|
623
415
|
/** Per-rule install nonce; 1 for a fresh install. */
|
|
624
416
|
installNonce: z.number().int().positive(),
|
|
625
417
|
/** Optional RPC URL override. Defaults to the pinned RPC for the
|
|
@@ -639,17 +431,6 @@ export const InstallPolicyInputSchema = z
|
|
|
639
431
|
* everything. Selecting `network: 'mainnet'` is NOT an opt-in -
|
|
640
432
|
* the mainnet pin is its own deny-by-default anchor. */
|
|
641
433
|
allowUnpinnedInterpreter: z.boolean().optional(),
|
|
642
|
-
/** Opt-in to installing when a signer of this rule can already reach the
|
|
643
|
-
* same calls through a context rule that has NO policy attached. OZ lets
|
|
644
|
-
* the signer choose which rule authorises a call and enforces only that
|
|
645
|
-
* rule's policies, so an unpoliced rule covering the same calls makes
|
|
646
|
-
* this policy decorative. Default-deny, because the caller almost
|
|
647
|
-
* certainly believes they are restricting something. */
|
|
648
|
-
allowAuthorityOverlap: z.boolean().optional(),
|
|
649
|
-
/** Skip the cross-rule authority scan entirely. The scan costs one RPC
|
|
650
|
-
* read per rule on the account; skipping it means the response carries
|
|
651
|
-
* no statement about what the signers can already do. */
|
|
652
|
-
skipAuthorityScan: z.boolean().optional(),
|
|
653
434
|
/** Base fee in stroops; defaults to BASE_FEE (100). */
|
|
654
435
|
baseFee: z.number().int().positive().optional(),
|
|
655
436
|
})
|
|
@@ -658,36 +439,6 @@ export const InstallPolicyInputSchema = z
|
|
|
658
439
|
})
|
|
659
440
|
export type InstallPolicyInput = z.infer<typeof InstallPolicyInputSchema>
|
|
660
441
|
|
|
661
|
-
/** `merge_policy` input.
|
|
662
|
-
*
|
|
663
|
-
* The tightening remedy for a cross-rule overlap: replace a rule's predicate
|
|
664
|
-
* with the conjunction of it and a new one. Two transactions, in order,
|
|
665
|
-
* because OZ refuses to re-attach a policy already on the rule - so the
|
|
666
|
-
* caller runs `detach`, waits for it to confirm, then runs `reinstall`. */
|
|
667
|
-
export const MergePolicyInputSchema = z
|
|
668
|
-
.object({
|
|
669
|
-
smartAccount: z
|
|
670
|
-
.string()
|
|
671
|
-
.regex(STELLAR_CONTRACT_ADDRESS, 'smartAccount must be a Stellar contract address (C...)'),
|
|
672
|
-
sourceAccount: z
|
|
673
|
-
.string()
|
|
674
|
-
.regex(STELLAR_ACCOUNT_ADDRESS, 'sourceAccount must be a Stellar account address (G...)'),
|
|
675
|
-
/** The rule whose predicate is being tightened. */
|
|
676
|
-
ruleId: z.number().int().nonnegative(),
|
|
677
|
-
/** The predicate to conjoin, base64 canonical ScVal, as emitted by
|
|
678
|
-
* `synthesize_policy`. */
|
|
679
|
-
incomingPredicateBlobBase64: z.string().min(1),
|
|
680
|
-
/** Which half of the remedy to build. */
|
|
681
|
-
step: z.enum(['detach', 'reinstall']),
|
|
682
|
-
network: NetworkSchema.optional(),
|
|
683
|
-
rpcUrl: z.string().url().optional(),
|
|
684
|
-
allowUnpinnedRpcUrl: z.boolean().optional(),
|
|
685
|
-
baseFee: z.number().int().positive().optional(),
|
|
686
|
-
})
|
|
687
|
-
.strict()
|
|
688
|
-
|
|
689
|
-
export type MergePolicyInput = z.infer<typeof MergePolicyInputSchema>
|
|
690
|
-
|
|
691
442
|
export const RevokePolicyInputSchema = z
|
|
692
443
|
.object({
|
|
693
444
|
/** The smart account contract address (C...). */
|