@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.
- package/README.md +6 -5
- 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 -11
- package/dist/install/build-add-context-rule.js +2 -97
- package/dist/install/build-install-policy.d.ts +4 -53
- package/dist/install/build-install-policy.js +44 -123
- 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 +35 -53
- package/dist/run/index.js +115 -156
- package/dist/run/schemas.d.ts +168 -1650
- package/dist/run/schemas.js +36 -199
- 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 -11
- package/dist-cjs/install/build-add-context-rule.js +1 -96
- package/dist-cjs/install/build-install-policy.d.ts +4 -53
- package/dist-cjs/install/build-install-policy.js +44 -122
- 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 +35 -53
- package/dist-cjs/run/index.js +116 -162
- package/dist-cjs/run/schemas.d.ts +168 -1650
- package/dist-cjs/run/schemas.js +37 -200
- 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 +4 -127
- package/src/install/build-install-policy.ts +93 -214
- 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 +130 -186
- package/src/run/schemas.ts +38 -214
- 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/build-install-predicate.d.ts +0 -96
- package/dist/install/build-install-predicate.js +0 -444
- package/dist-cjs/install/build-install-predicate.d.ts +0 -96
- package/dist-cjs/install/build-install-predicate.js +0 -479
- 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/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/index.ts
CHANGED
|
@@ -21,16 +21,12 @@ import { createHash } from 'node:crypto'
|
|
|
21
21
|
import { rpc } from '@stellar/stellar-sdk'
|
|
22
22
|
import {
|
|
23
23
|
type ErrorCode,
|
|
24
|
-
type MandateSpec,
|
|
25
24
|
type Network,
|
|
26
|
-
type OzAdapterConfig,
|
|
27
25
|
type PredicateNode,
|
|
28
26
|
type ProposedPolicy,
|
|
29
|
-
placeholderOzConfig,
|
|
30
27
|
type RecordedTransaction,
|
|
31
28
|
recordTransaction,
|
|
32
29
|
type SynthesizeFromRecordingOptions,
|
|
33
|
-
synthesizeFromMandate,
|
|
34
30
|
synthesizeFromRecording,
|
|
35
31
|
type ToolError,
|
|
36
32
|
type ToolResponse,
|
|
@@ -44,8 +40,7 @@ import {
|
|
|
44
40
|
rpcClientFromServer,
|
|
45
41
|
} from '../install/build-install-policy.ts'
|
|
46
42
|
import { getInterpreterInfo } from '../install/get-interpreter-info.ts'
|
|
47
|
-
import type
|
|
48
|
-
import { simulatePolicy, verifyPolicy } from '../verify/index.ts'
|
|
43
|
+
import { type EvalContext, evaluate, generateCases } from '../simulate/index.ts'
|
|
49
44
|
import {
|
|
50
45
|
type GetInterpreterInfoInput,
|
|
51
46
|
GetInterpreterInfoInputSchema,
|
|
@@ -87,10 +82,7 @@ export {
|
|
|
87
82
|
InstallPolicyInputSchema,
|
|
88
83
|
InterpreterOptionsSchema,
|
|
89
84
|
MAINNET_RPC_URL,
|
|
90
|
-
MandateSpecSchema,
|
|
91
85
|
NetworkSchema,
|
|
92
|
-
OraclePriceFixtureSchema,
|
|
93
|
-
OzAdapterConfigSchema,
|
|
94
86
|
PINNED_INTERPRETER_ADDRESS_BY_NETWORK,
|
|
95
87
|
PINNED_INTERPRETER_GRAMMAR_VERSION,
|
|
96
88
|
PINNED_INTERPRETER_MAINNET_ADDRESS,
|
|
@@ -102,11 +94,9 @@ export {
|
|
|
102
94
|
RecordTransactionInputSchema,
|
|
103
95
|
RevokePolicyInputSchema,
|
|
104
96
|
RPC_URL_BY_NETWORK,
|
|
105
|
-
SimulatePolicyInputSchema,
|
|
106
97
|
SynthesizePolicyInputSchema,
|
|
107
98
|
TESTNET_RPC_URL,
|
|
108
99
|
ToolErrorSchema,
|
|
109
|
-
VerifyPolicyInputSchema,
|
|
110
100
|
} from './schemas.ts'
|
|
111
101
|
|
|
112
102
|
export type RunRecordTransactionInput = RecordTransactionInput
|
|
@@ -152,10 +142,7 @@ export async function runRecordTransaction(
|
|
|
152
142
|
): Promise<ToolResponse<RecordedTransaction>> {
|
|
153
143
|
const parsed = RecordTransactionInputSchema.safeParse(raw)
|
|
154
144
|
if (!parsed.success) {
|
|
155
|
-
return {
|
|
156
|
-
ok: false,
|
|
157
|
-
error: validationError('record_transaction', parsed.error.issues),
|
|
158
|
-
}
|
|
145
|
+
return { ok: false, error: validationError('record_transaction', parsed.error.issues) }
|
|
159
146
|
}
|
|
160
147
|
const input: RecordTransactionInput = parsed.data
|
|
161
148
|
// Strip the wire-only `confidenceOverride` and pass the rest straight through.
|
|
@@ -170,15 +157,11 @@ export async function runRecordTransaction(
|
|
|
170
157
|
try {
|
|
171
158
|
return await recordTransaction(coreInput)
|
|
172
159
|
} catch (e) {
|
|
173
|
-
return
|
|
174
|
-
ok: false,
|
|
175
|
-
error: caughtError('record_transaction', 'RECORDING_FAILED', e),
|
|
176
|
-
}
|
|
160
|
+
return toolFailure('record_transaction', e)
|
|
177
161
|
}
|
|
178
162
|
}
|
|
179
163
|
|
|
180
164
|
/** `synthesize_policy` body - discriminated union on `source`:
|
|
181
|
-
* - `mandate` -> synthesizeFromMandate
|
|
182
165
|
* - `recording` -> synthesizeFromRecording
|
|
183
166
|
* Exposing BOTH front-ends through ONE tool keeps the MCP surface tiny while
|
|
184
167
|
* letting the agent pick the deterministic or the inferred path. The CLI
|
|
@@ -193,157 +176,37 @@ export async function runSynthesizePolicy(raw: unknown): Promise<
|
|
|
193
176
|
ToolResponse<ProposedPolicy> & {
|
|
194
177
|
explain?: {
|
|
195
178
|
predicateTree: PredicateNode | null
|
|
196
|
-
simulation: SimulationResult
|
|
197
179
|
}
|
|
198
180
|
}
|
|
199
181
|
> {
|
|
200
182
|
const parsed = SynthesizePolicyInputSchema.safeParse(raw)
|
|
201
183
|
if (!parsed.success) {
|
|
202
|
-
return {
|
|
203
|
-
ok: false,
|
|
204
|
-
error: validationError('synthesize_policy', parsed.error.issues),
|
|
205
|
-
}
|
|
184
|
+
return { ok: false, error: validationError('synthesize_policy', parsed.error.issues) }
|
|
206
185
|
}
|
|
207
186
|
const input = parsed.data
|
|
208
|
-
const ozConfig: OzAdapterConfig = resolveOzConfig(input)
|
|
209
187
|
|
|
210
188
|
try {
|
|
211
|
-
if (input.source === 'mandate') {
|
|
212
|
-
// Zod's optional fields widen to `T | undefined`, which the core's
|
|
213
|
-
// exact-optional MandateSpec rejects; the schema already validated the
|
|
214
|
-
// shape, so assert it (same pattern as the recordedTx cast below).
|
|
215
|
-
return await synthesizeFromMandate(
|
|
216
|
-
input.mandate as MandateSpec,
|
|
217
|
-
ozConfig,
|
|
218
|
-
input.explain === true ? { explain: true } : {}
|
|
219
|
-
)
|
|
220
|
-
}
|
|
221
|
-
// recording source
|
|
222
189
|
const recorded: RecordedTransaction = input.recordedTx as RecordedTransaction
|
|
223
|
-
return await synthesizeFromRecording(
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
...(input.confidenceOverride !== undefined
|
|
229
|
-
? { confidenceOverride: input.confidenceOverride }
|
|
230
|
-
: {}),
|
|
231
|
-
...(input.interpreter !== undefined ? { interpreter: input.interpreter } : {}),
|
|
232
|
-
...(input.explain === true ? { explain: true } : {}),
|
|
233
|
-
} as SynthesizeFromRecordingOptions,
|
|
234
|
-
ozConfig
|
|
235
|
-
)
|
|
236
|
-
} catch (e) {
|
|
237
|
-
return {
|
|
238
|
-
ok: false,
|
|
239
|
-
error: caughtError('synthesize_policy', 'SYNTHESIS_ERROR', e),
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function resolveOzConfig(input: SynthesizePolicyInput): OzAdapterConfig {
|
|
245
|
-
if (input.ozConfig) return input.ozConfig
|
|
246
|
-
// The mandate path is network-agnostic; fall back to mainnet so the
|
|
247
|
-
// placeholder OZ instance addresses are deterministic.
|
|
248
|
-
return placeholderOzConfig('mainnet')
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** `simulate_policy` body - thin wrapper over `simulatePolicy`. The engine
|
|
252
|
-
* already returns fail-closed `{ok:false, error}` for runtime failures
|
|
253
|
-
* (SIMULATION_ERROR), so the try/catch envelope is for raw SDK throws
|
|
254
|
-
* only - same pattern as the other two wrappers. The predicate is
|
|
255
|
-
* passed inline (stateless by design; no `proposed_policy_id` lookup). */
|
|
256
|
-
export async function runSimulatePolicy(raw: unknown): Promise<ToolResponse<SimulationResult>> {
|
|
257
|
-
const parsed = SimulatePolicyInputSchema.safeParse(raw)
|
|
258
|
-
if (!parsed.success) {
|
|
259
|
-
return {
|
|
260
|
-
ok: false,
|
|
261
|
-
error: validationError('simulate_policy', parsed.error.issues),
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const input: SimulatePolicyInput = parsed.data
|
|
265
|
-
try {
|
|
266
|
-
// The recursive PredicateNodeSchema + ContractInvocationSchema are
|
|
267
|
-
// typed `z.ZodType<unknown>` to survive TS's circular inference; the
|
|
268
|
-
// engine wants typed `PredicateNode | null` + `RecordedTransaction`.
|
|
269
|
-
// The schema already validated the shape, so assert through the
|
|
270
|
-
// unknown back to the core types. Same pattern as the recordedTx
|
|
271
|
-
// cast in `runSynthesizePolicy`.
|
|
272
|
-
return simulatePolicy(
|
|
273
|
-
input.predicate as PredicateNode | null,
|
|
274
|
-
input.permitTx as RecordedTransaction,
|
|
275
|
-
{
|
|
276
|
-
...(input.validUntilLedger !== undefined
|
|
277
|
-
? { validUntilLedger: input.validUntilLedger }
|
|
278
|
-
: {}),
|
|
279
|
-
...(input.oraclePricesByAsset !== undefined
|
|
280
|
-
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
281
|
-
: {}),
|
|
282
|
-
}
|
|
283
|
-
)
|
|
284
|
-
} catch (e) {
|
|
285
|
-
return {
|
|
286
|
-
ok: false,
|
|
287
|
-
error: caughtError('simulate_policy', 'SIMULATION_ERROR', e),
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/** `verify_policy` body - thin wrapper over `verifyPolicy`. The engine
|
|
293
|
-
* already returns `{ok:false, error}` with code VERIFICATION_FAILED when
|
|
294
|
-
* the minimality check fails; the try/catch envelope is for raw SDK
|
|
295
|
-
* throws only. Mirrors `runSimulatePolicy` exactly. */
|
|
296
|
-
export async function runVerifyPolicy(raw: unknown): Promise<ToolResponse<true>> {
|
|
297
|
-
const parsed = VerifyPolicyInputSchema.safeParse(raw)
|
|
298
|
-
if (!parsed.success) {
|
|
299
|
-
return {
|
|
300
|
-
ok: false,
|
|
301
|
-
error: validationError('verify_policy', parsed.error.issues),
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
const input: VerifyPolicyInput = parsed.data
|
|
305
|
-
try {
|
|
306
|
-
// Same cast as runSimulatePolicy: the recursive schemas are typed
|
|
307
|
-
// `unknown`; the engine wants typed `PredicateNode` +
|
|
308
|
-
// `RecordedTransaction`. The schema already validated the shape.
|
|
309
|
-
return verifyPolicy(input.predicate as PredicateNode, input.permitTx as RecordedTransaction, {
|
|
310
|
-
...(input.validUntilLedger !== undefined ? { validUntilLedger: input.validUntilLedger } : {}),
|
|
311
|
-
...(input.oraclePricesByAsset !== undefined
|
|
312
|
-
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
190
|
+
return await synthesizeFromRecording(recorded, {
|
|
191
|
+
network: input.network,
|
|
192
|
+
...(input.userResponses !== undefined ? { userResponses: input.userResponses } : {}),
|
|
193
|
+
...(input.confidenceOverride !== undefined
|
|
194
|
+
? { confidenceOverride: input.confidenceOverride }
|
|
313
195
|
: {}),
|
|
314
|
-
|
|
196
|
+
...(input.interpreter !== undefined ? { interpreter: input.interpreter } : {}),
|
|
197
|
+
...(input.explain === true ? { explain: true } : {}),
|
|
198
|
+
} as SynthesizeFromRecordingOptions)
|
|
315
199
|
} catch (e) {
|
|
316
|
-
return
|
|
317
|
-
ok: false,
|
|
318
|
-
error: caughtError('verify_policy', 'VERIFICATION_FAILED', e),
|
|
319
|
-
}
|
|
200
|
+
return toolFailure('synthesize_policy', e)
|
|
320
201
|
}
|
|
321
202
|
}
|
|
322
203
|
|
|
323
|
-
/** `install_policy` body - thin wrapper over `buildInstallPolicyXdr`.
|
|
324
|
-
* Returns the unsigned Soroban transaction envelope (base64 XDR) the
|
|
325
|
-
* wallet signs. The wallet signature IS the user-confirmation step - no
|
|
326
|
-
* `action_id` two-call pair (the server is stateless, see server.ts:10-12).
|
|
327
|
-
* One call installs the policy outright: `add_context_rule` carries the
|
|
328
|
-
* predicate to the interpreter in its `policies` install_param, so no
|
|
329
|
-
* separate `interpreter.install` call is needed or possible.
|
|
330
|
-
*
|
|
331
|
-
* Default-deny: an interpreter policy address other than the pinned
|
|
332
|
-
* interpreter for the selected network is REFUSED (the smart account
|
|
333
|
-
* would delegate to an interpreter the caller controls); the same
|
|
334
|
-
* applies to a non-pinned RPC URL (the auth nonce the wallet signs
|
|
335
|
-
* comes from the RPC). Both gates accept an explicit opt-in flag.
|
|
336
|
-
* Pin selection follows `input.network` (defaults to `testnet` so the
|
|
337
|
-
* pre-mainnet callers keep working unchanged). */
|
|
338
204
|
export async function runInstallPolicy(
|
|
339
205
|
raw: unknown
|
|
340
206
|
): Promise<ToolResponse<BuildInstallPolicyResult>> {
|
|
341
207
|
const parsed = InstallPolicyInputSchema.safeParse(raw)
|
|
342
208
|
if (!parsed.success) {
|
|
343
|
-
return {
|
|
344
|
-
ok: false,
|
|
345
|
-
error: validationError('install_policy', parsed.error.issues),
|
|
346
|
-
}
|
|
209
|
+
return { ok: false, error: validationError('install_policy', parsed.error.issues) }
|
|
347
210
|
}
|
|
348
211
|
const input: InstallPolicyInput = parsed.data
|
|
349
212
|
const network: Network = input.network ?? 'testnet'
|
|
@@ -372,10 +235,7 @@ export async function runInstallPolicy(
|
|
|
372
235
|
try {
|
|
373
236
|
rpcClient = buildRpcClientFromInput(input.rpcUrl, network)
|
|
374
237
|
} catch (e) {
|
|
375
|
-
return
|
|
376
|
-
ok: false,
|
|
377
|
-
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
378
|
-
}
|
|
238
|
+
return toolFailure('install_policy', e)
|
|
379
239
|
}
|
|
380
240
|
try {
|
|
381
241
|
const interpreterPolicy = input.rule.policies.find((p) => p.kind === 'interpreter')
|
|
@@ -396,10 +256,7 @@ export async function runInstallPolicy(
|
|
|
396
256
|
})
|
|
397
257
|
return { ok: true, data: result }
|
|
398
258
|
} catch (e) {
|
|
399
|
-
return
|
|
400
|
-
ok: false,
|
|
401
|
-
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
402
|
-
}
|
|
259
|
+
return toolFailure('install_policy', e)
|
|
403
260
|
}
|
|
404
261
|
}
|
|
405
262
|
|
|
@@ -417,10 +274,7 @@ export async function runRevokePolicy(
|
|
|
417
274
|
): Promise<ToolResponse<BuildRevokePolicyResult>> {
|
|
418
275
|
const parsed = RevokePolicyInputSchema.safeParse(raw)
|
|
419
276
|
if (!parsed.success) {
|
|
420
|
-
return {
|
|
421
|
-
ok: false,
|
|
422
|
-
error: validationError('revoke_policy', parsed.error.issues),
|
|
423
|
-
}
|
|
277
|
+
return { ok: false, error: validationError('revoke_policy', parsed.error.issues) }
|
|
424
278
|
}
|
|
425
279
|
const input: RevokePolicyInput = parsed.data
|
|
426
280
|
const network: Network = input.network ?? 'testnet'
|
|
@@ -439,10 +293,7 @@ export async function runRevokePolicy(
|
|
|
439
293
|
try {
|
|
440
294
|
rpcClient = buildRpcClientFromInput(input.rpcUrl, network)
|
|
441
295
|
} catch (e) {
|
|
442
|
-
return
|
|
443
|
-
ok: false,
|
|
444
|
-
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
445
|
-
}
|
|
296
|
+
return toolFailure('revoke_policy', e)
|
|
446
297
|
}
|
|
447
298
|
try {
|
|
448
299
|
const result = await buildRevokePolicyXdr({
|
|
@@ -455,10 +306,7 @@ export async function runRevokePolicy(
|
|
|
455
306
|
})
|
|
456
307
|
return { ok: true, data: result }
|
|
457
308
|
} catch (e) {
|
|
458
|
-
return
|
|
459
|
-
ok: false,
|
|
460
|
-
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
461
|
-
}
|
|
309
|
+
return toolFailure('revoke_policy', e)
|
|
462
310
|
}
|
|
463
311
|
}
|
|
464
312
|
|
|
@@ -481,15 +329,114 @@ export async function runRevokePolicy(
|
|
|
481
329
|
* a non-pinned `rpcUrl` would silently bind the caller to a host they
|
|
482
330
|
* picked. The pin is enforced here too, with the same `allowUnpinnedRpcUrl`
|
|
483
331
|
* opt-in as install/revoke. */
|
|
332
|
+
/** The call a predicate is evaluated against: the single top-level invocation
|
|
333
|
+
* the smart account authorises. `Policy::enforce` receives one `Context`, not
|
|
334
|
+
* a sub-invocation tree, so simulating anything deeper would claim a
|
|
335
|
+
* guarantee the contract does not make. */
|
|
336
|
+
function evalContextFromRecording(tx: RecordedTransaction): EvalContext | null {
|
|
337
|
+
const top = tx.invocations[0]
|
|
338
|
+
if (!top) return null
|
|
339
|
+
return { contract: top.contract, fn: top.fn, args: top.args }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Both `simulate_policy` and `verify_policy` evaluate against a context derived
|
|
343
|
+
* from `permitTx`; neither has anything to evaluate when the recording carries
|
|
344
|
+
* no top-level invocation. */
|
|
345
|
+
function noInvocationError(toolName: 'simulate_policy' | 'verify_policy'): ToolError {
|
|
346
|
+
return {
|
|
347
|
+
code: TOOL_ERROR_CODE[toolName],
|
|
348
|
+
message: `${toolName}: permitTx carries no invocation to evaluate`,
|
|
349
|
+
severity: 'error',
|
|
350
|
+
retryable: false,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** `simulate_policy` body - evaluate a predicate against one recorded call.
|
|
355
|
+
*
|
|
356
|
+
* The evaluator is a second implementation of the on-chain semantics, and the
|
|
357
|
+
* conformance harness asserts it agrees with the Rust interpreter case for
|
|
358
|
+
* case. A verdict here is therefore a claim about what the contract would do,
|
|
359
|
+
* not a guess. */
|
|
360
|
+
export function runSimulatePolicy(raw: unknown): ToolResponse<{
|
|
361
|
+
permitted: boolean
|
|
362
|
+
reason: string | null
|
|
363
|
+
call: { contract: string; fn: string; argCount: number }
|
|
364
|
+
}> {
|
|
365
|
+
const parsed = SimulatePolicyInputSchema.safeParse(raw)
|
|
366
|
+
if (!parsed.success) {
|
|
367
|
+
return { ok: false, error: validationError('simulate_policy', parsed.error.issues) }
|
|
368
|
+
}
|
|
369
|
+
const input: SimulatePolicyInput = parsed.data
|
|
370
|
+
const ctx = evalContextFromRecording(input.permitTx as RecordedTransaction)
|
|
371
|
+
if (!ctx) return { ok: false, error: noInvocationError('simulate_policy') }
|
|
372
|
+
try {
|
|
373
|
+
const res = evaluate(input.predicate as PredicateNode, ctx)
|
|
374
|
+
return {
|
|
375
|
+
ok: true,
|
|
376
|
+
data: {
|
|
377
|
+
permitted: res.permit,
|
|
378
|
+
reason: res.permit ? null : res.reason,
|
|
379
|
+
call: { contract: ctx.contract, fn: ctx.fn, argCount: ctx.args.length },
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
} catch (e) {
|
|
383
|
+
return toolFailure('simulate_policy', e)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** `verify_policy` body - the permit case plus a generated deny case per
|
|
388
|
+
* dimension.
|
|
389
|
+
*
|
|
390
|
+
* Two failure modes are being checked, and they are not the same thing. A
|
|
391
|
+
* permit case that denies means the policy is too STRICT: it would refuse the
|
|
392
|
+
* very transaction it was synthesised from. A deny case that permits means it
|
|
393
|
+
* is too LOOSE: some mutation of that transaction still gets through. `ok` is
|
|
394
|
+
* true only when neither holds. */
|
|
395
|
+
export function runVerifyPolicy(raw: unknown): ToolResponse<{
|
|
396
|
+
ok: boolean
|
|
397
|
+
permit: { permitted: boolean; reason: string | null }
|
|
398
|
+
denies: Array<{ dimension: string; denied: boolean; reason: string | null }>
|
|
399
|
+
dimensionsCovered: number
|
|
400
|
+
}> {
|
|
401
|
+
const parsed = VerifyPolicyInputSchema.safeParse(raw)
|
|
402
|
+
if (!parsed.success) {
|
|
403
|
+
return { ok: false, error: validationError('verify_policy', parsed.error.issues) }
|
|
404
|
+
}
|
|
405
|
+
const input: VerifyPolicyInput = parsed.data
|
|
406
|
+
const ctx = evalContextFromRecording(input.permitTx as RecordedTransaction)
|
|
407
|
+
if (!ctx) return { ok: false, error: noInvocationError('verify_policy') }
|
|
408
|
+
try {
|
|
409
|
+
const predicate = input.predicate as PredicateNode
|
|
410
|
+
const cases = generateCases(predicate, ctx)
|
|
411
|
+
const permitRes = evaluate(predicate, cases.permit)
|
|
412
|
+
const denies = cases.denies.map((d) => {
|
|
413
|
+
const r = evaluate(predicate, d.ctx)
|
|
414
|
+
return {
|
|
415
|
+
dimension: d.dimension,
|
|
416
|
+
denied: !r.permit,
|
|
417
|
+
reason: r.permit ? null : r.reason,
|
|
418
|
+
}
|
|
419
|
+
})
|
|
420
|
+
return {
|
|
421
|
+
ok: true,
|
|
422
|
+
data: {
|
|
423
|
+
ok: permitRes.permit && denies.every((d) => d.denied),
|
|
424
|
+
permit: { permitted: permitRes.permit, reason: permitRes.permit ? null : permitRes.reason },
|
|
425
|
+
denies,
|
|
426
|
+
dimensionsCovered: denies.length,
|
|
427
|
+
},
|
|
428
|
+
}
|
|
429
|
+
} catch (e) {
|
|
430
|
+
return toolFailure('verify_policy', e)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
484
434
|
export async function runGetInterpreterInfo(
|
|
485
435
|
raw: unknown
|
|
486
436
|
): Promise<ToolResponse<ReturnType<typeof getInterpreterInfo>>> {
|
|
487
437
|
const parsed = GetInterpreterInfoInputSchema.safeParse(raw)
|
|
488
438
|
if (!parsed.success) {
|
|
489
|
-
return {
|
|
490
|
-
ok: false,
|
|
491
|
-
error: validationError('get_interpreter_info', parsed.error.issues),
|
|
492
|
-
}
|
|
439
|
+
return { ok: false, error: validationError('get_interpreter_info', parsed.error.issues) }
|
|
493
440
|
}
|
|
494
441
|
const input: GetInterpreterInfoInput = parsed.data
|
|
495
442
|
const network: Network = input.network ?? 'testnet'
|
|
@@ -528,10 +475,7 @@ export async function runGetInterpreterInfo(
|
|
|
528
475
|
})
|
|
529
476
|
return { ok: true, data: info }
|
|
530
477
|
} catch (e) {
|
|
531
|
-
return
|
|
532
|
-
ok: false,
|
|
533
|
-
error: caughtError('get_interpreter_info', 'RECORDING_FAILED', e),
|
|
534
|
-
}
|
|
478
|
+
return toolFailure('get_interpreter_info', e)
|
|
535
479
|
}
|
|
536
480
|
}
|
|
537
481
|
|
|
@@ -597,14 +541,8 @@ function enforceRpcPin(
|
|
|
597
541
|
network: Network
|
|
598
542
|
): ToolError | null {
|
|
599
543
|
if (!rpcUrl || rpcUrl === expectedRpc || allowUnpinned === true) return null
|
|
600
|
-
const code: ErrorCode =
|
|
601
|
-
toolName === 'install_policy'
|
|
602
|
-
? 'INSTALL_BUILD_FAILED'
|
|
603
|
-
: toolName === 'revoke_policy'
|
|
604
|
-
? 'REVOKE_BUILD_FAILED'
|
|
605
|
-
: 'RECORDING_FAILED'
|
|
606
544
|
return {
|
|
607
|
-
code,
|
|
545
|
+
code: TOOL_ERROR_CODE[toolName],
|
|
608
546
|
message: `${toolName}: rpcUrl must equal the pinned ${expectedRpc} (${network}); set allowUnpinnedRpcUrl: true to opt in to a custom endpoint`,
|
|
609
547
|
severity: 'error',
|
|
610
548
|
retryable: false,
|
|
@@ -636,6 +574,12 @@ function validationError(
|
|
|
636
574
|
* error in `details` for the agent to inspect. Exported as a test-only seam
|
|
637
575
|
* so the suite in run/index.test.ts can drive the envelope path without
|
|
638
576
|
* standing up a full recordTransaction pipeline. */
|
|
577
|
+
/** The fail-closed return every tool body uses in its `catch`. Taking only the
|
|
578
|
+
* tool name keeps the tool -> error-code mapping in TOOL_ERROR_CODE alone. */
|
|
579
|
+
function toolFailure(toolName: RunToolName, e: unknown): { ok: false; error: ToolError } {
|
|
580
|
+
return { ok: false, error: caughtError(toolName, TOOL_ERROR_CODE[toolName], e) }
|
|
581
|
+
}
|
|
582
|
+
|
|
639
583
|
export function caughtError(toolName: RunToolName, code: ErrorCode, e: unknown): ToolError {
|
|
640
584
|
return {
|
|
641
585
|
code,
|