@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/dist/run/index.js
CHANGED
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
// drive the CLI (which calls into the same core directly without MCP).
|
|
19
19
|
import { createHash } from 'node:crypto';
|
|
20
20
|
import { rpc } from '@stellar/stellar-sdk';
|
|
21
|
-
import {
|
|
21
|
+
import { recordTransaction, synthesizeFromRecording, } from "../index.js";
|
|
22
22
|
import { buildInstallPolicyXdr, buildRevokePolicyXdr, rpcClientFromServer, } from "../install/build-install-policy.js";
|
|
23
23
|
import { getInterpreterInfo } from "../install/get-interpreter-info.js";
|
|
24
|
-
import {
|
|
24
|
+
import { evaluate, generateCases } from "../simulate/index.js";
|
|
25
25
|
import { GetInterpreterInfoInputSchema, InstallPolicyInputSchema, NETWORK_PASSPHRASES, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_WASM_SHA256, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SimulatePolicyInputSchema, SynthesizePolicyInputSchema, VerifyPolicyInputSchema, } from "./schemas.js";
|
|
26
26
|
// Re-export the underlying Zod schemas so the MCP package (and any other
|
|
27
27
|
// downstream consumer) can import the canonical input shapes from the same
|
|
28
28
|
// module that owns the tool-body glue. The strict schemas are the source of
|
|
29
29
|
// truth - MCP tool shapes are derived from them.
|
|
30
|
-
export { ComposeUserResponsesSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL,
|
|
30
|
+
export { ComposeUserResponsesSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MAINNET_RPC_URL, NetworkSchema, PINNED_INTERPRETER_ADDRESS_BY_NETWORK, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_MAINNET_ADDRESS, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, RPC_URL_BY_NETWORK, SynthesizePolicyInputSchema, TESTNET_RPC_URL, ToolErrorSchema, } from "./schemas.js";
|
|
31
31
|
/** Map every tool name to its canonical domain error code. Replaces a 7-way
|
|
32
32
|
* if/else so adding a tool adds one line here rather than a new branch in
|
|
33
33
|
* each envelope call. */
|
|
@@ -54,10 +54,7 @@ const TOOL_ERROR_CODE = {
|
|
|
54
54
|
export async function runRecordTransaction(raw) {
|
|
55
55
|
const parsed = RecordTransactionInputSchema.safeParse(raw);
|
|
56
56
|
if (!parsed.success) {
|
|
57
|
-
return {
|
|
58
|
-
ok: false,
|
|
59
|
-
error: validationError('record_transaction', parsed.error.issues),
|
|
60
|
-
};
|
|
57
|
+
return { ok: false, error: validationError('record_transaction', parsed.error.issues) };
|
|
61
58
|
}
|
|
62
59
|
const input = parsed.data;
|
|
63
60
|
// Strip the wire-only `confidenceOverride` and pass the rest straight through.
|
|
@@ -73,14 +70,10 @@ export async function runRecordTransaction(raw) {
|
|
|
73
70
|
return await recordTransaction(coreInput);
|
|
74
71
|
}
|
|
75
72
|
catch (e) {
|
|
76
|
-
return
|
|
77
|
-
ok: false,
|
|
78
|
-
error: caughtError('record_transaction', 'RECORDING_FAILED', e),
|
|
79
|
-
};
|
|
73
|
+
return toolFailure('record_transaction', e);
|
|
80
74
|
}
|
|
81
75
|
}
|
|
82
76
|
/** `synthesize_policy` body - discriminated union on `source`:
|
|
83
|
-
* - `mandate` -> synthesizeFromMandate
|
|
84
77
|
* - `recording` -> synthesizeFromRecording
|
|
85
78
|
* Exposing BOTH front-ends through ONE tool keeps the MCP surface tiny while
|
|
86
79
|
* letting the agent pick the deterministic or the inferred path. The CLI
|
|
@@ -94,21 +87,10 @@ export async function runRecordTransaction(raw) {
|
|
|
94
87
|
export async function runSynthesizePolicy(raw) {
|
|
95
88
|
const parsed = SynthesizePolicyInputSchema.safeParse(raw);
|
|
96
89
|
if (!parsed.success) {
|
|
97
|
-
return {
|
|
98
|
-
ok: false,
|
|
99
|
-
error: validationError('synthesize_policy', parsed.error.issues),
|
|
100
|
-
};
|
|
90
|
+
return { ok: false, error: validationError('synthesize_policy', parsed.error.issues) };
|
|
101
91
|
}
|
|
102
92
|
const input = parsed.data;
|
|
103
|
-
const ozConfig = resolveOzConfig(input);
|
|
104
93
|
try {
|
|
105
|
-
if (input.source === 'mandate') {
|
|
106
|
-
// Zod's optional fields widen to `T | undefined`, which the core's
|
|
107
|
-
// exact-optional MandateSpec rejects; the schema already validated the
|
|
108
|
-
// shape, so assert it (same pattern as the recordedTx cast below).
|
|
109
|
-
return await synthesizeFromMandate(input.mandate, ozConfig, input.explain === true ? { explain: true } : {});
|
|
110
|
-
}
|
|
111
|
-
// recording source
|
|
112
94
|
const recorded = input.recordedTx;
|
|
113
95
|
return await synthesizeFromRecording(recorded, {
|
|
114
96
|
network: input.network,
|
|
@@ -118,112 +100,16 @@ export async function runSynthesizePolicy(raw) {
|
|
|
118
100
|
: {}),
|
|
119
101
|
...(input.interpreter !== undefined ? { interpreter: input.interpreter } : {}),
|
|
120
102
|
...(input.explain === true ? { explain: true } : {}),
|
|
121
|
-
}, ozConfig);
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
return {
|
|
125
|
-
ok: false,
|
|
126
|
-
error: caughtError('synthesize_policy', 'SYNTHESIS_ERROR', e),
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function resolveOzConfig(input) {
|
|
131
|
-
if (input.ozConfig)
|
|
132
|
-
return input.ozConfig;
|
|
133
|
-
// The mandate path is network-agnostic; fall back to mainnet so the
|
|
134
|
-
// placeholder OZ instance addresses are deterministic.
|
|
135
|
-
return placeholderOzConfig('mainnet');
|
|
136
|
-
}
|
|
137
|
-
/** `simulate_policy` body - thin wrapper over `simulatePolicy`. The engine
|
|
138
|
-
* already returns fail-closed `{ok:false, error}` for runtime failures
|
|
139
|
-
* (SIMULATION_ERROR), so the try/catch envelope is for raw SDK throws
|
|
140
|
-
* only - same pattern as the other two wrappers. The predicate is
|
|
141
|
-
* passed inline (stateless by design; no `proposed_policy_id` lookup). */
|
|
142
|
-
export async function runSimulatePolicy(raw) {
|
|
143
|
-
const parsed = SimulatePolicyInputSchema.safeParse(raw);
|
|
144
|
-
if (!parsed.success) {
|
|
145
|
-
return {
|
|
146
|
-
ok: false,
|
|
147
|
-
error: validationError('simulate_policy', parsed.error.issues),
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
const input = parsed.data;
|
|
151
|
-
try {
|
|
152
|
-
// The recursive PredicateNodeSchema + ContractInvocationSchema are
|
|
153
|
-
// typed `z.ZodType<unknown>` to survive TS's circular inference; the
|
|
154
|
-
// engine wants typed `PredicateNode | null` + `RecordedTransaction`.
|
|
155
|
-
// The schema already validated the shape, so assert through the
|
|
156
|
-
// unknown back to the core types. Same pattern as the recordedTx
|
|
157
|
-
// cast in `runSynthesizePolicy`.
|
|
158
|
-
return simulatePolicy(input.predicate, input.permitTx, {
|
|
159
|
-
...(input.validUntilLedger !== undefined
|
|
160
|
-
? { validUntilLedger: input.validUntilLedger }
|
|
161
|
-
: {}),
|
|
162
|
-
...(input.oraclePricesByAsset !== undefined
|
|
163
|
-
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
164
|
-
: {}),
|
|
165
103
|
});
|
|
166
104
|
}
|
|
167
105
|
catch (e) {
|
|
168
|
-
return
|
|
169
|
-
ok: false,
|
|
170
|
-
error: caughtError('simulate_policy', 'SIMULATION_ERROR', e),
|
|
171
|
-
};
|
|
106
|
+
return toolFailure('synthesize_policy', e);
|
|
172
107
|
}
|
|
173
108
|
}
|
|
174
|
-
/** `verify_policy` body - thin wrapper over `verifyPolicy`. The engine
|
|
175
|
-
* already returns `{ok:false, error}` with code VERIFICATION_FAILED when
|
|
176
|
-
* the minimality check fails; the try/catch envelope is for raw SDK
|
|
177
|
-
* throws only. Mirrors `runSimulatePolicy` exactly. */
|
|
178
|
-
export async function runVerifyPolicy(raw) {
|
|
179
|
-
const parsed = VerifyPolicyInputSchema.safeParse(raw);
|
|
180
|
-
if (!parsed.success) {
|
|
181
|
-
return {
|
|
182
|
-
ok: false,
|
|
183
|
-
error: validationError('verify_policy', parsed.error.issues),
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
const input = parsed.data;
|
|
187
|
-
try {
|
|
188
|
-
// Same cast as runSimulatePolicy: the recursive schemas are typed
|
|
189
|
-
// `unknown`; the engine wants typed `PredicateNode` +
|
|
190
|
-
// `RecordedTransaction`. The schema already validated the shape.
|
|
191
|
-
return verifyPolicy(input.predicate, input.permitTx, {
|
|
192
|
-
...(input.validUntilLedger !== undefined ? { validUntilLedger: input.validUntilLedger } : {}),
|
|
193
|
-
...(input.oraclePricesByAsset !== undefined
|
|
194
|
-
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
195
|
-
: {}),
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
catch (e) {
|
|
199
|
-
return {
|
|
200
|
-
ok: false,
|
|
201
|
-
error: caughtError('verify_policy', 'VERIFICATION_FAILED', e),
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
/** `install_policy` body - thin wrapper over `buildInstallPolicyXdr`.
|
|
206
|
-
* Returns the unsigned Soroban transaction envelope (base64 XDR) the
|
|
207
|
-
* wallet signs. The wallet signature IS the user-confirmation step - no
|
|
208
|
-
* `action_id` two-call pair (the server is stateless, see server.ts:10-12).
|
|
209
|
-
* One call installs the policy outright: `add_context_rule` carries the
|
|
210
|
-
* predicate to the interpreter in its `policies` install_param, so no
|
|
211
|
-
* separate `interpreter.install` call is needed or possible.
|
|
212
|
-
*
|
|
213
|
-
* Default-deny: an interpreter policy address other than the pinned
|
|
214
|
-
* interpreter for the selected network is REFUSED (the smart account
|
|
215
|
-
* would delegate to an interpreter the caller controls); the same
|
|
216
|
-
* applies to a non-pinned RPC URL (the auth nonce the wallet signs
|
|
217
|
-
* comes from the RPC). Both gates accept an explicit opt-in flag.
|
|
218
|
-
* Pin selection follows `input.network` (defaults to `testnet` so the
|
|
219
|
-
* pre-mainnet callers keep working unchanged). */
|
|
220
109
|
export async function runInstallPolicy(raw) {
|
|
221
110
|
const parsed = InstallPolicyInputSchema.safeParse(raw);
|
|
222
111
|
if (!parsed.success) {
|
|
223
|
-
return {
|
|
224
|
-
ok: false,
|
|
225
|
-
error: validationError('install_policy', parsed.error.issues),
|
|
226
|
-
};
|
|
112
|
+
return { ok: false, error: validationError('install_policy', parsed.error.issues) };
|
|
227
113
|
}
|
|
228
114
|
const input = parsed.data;
|
|
229
115
|
const network = input.network ?? 'testnet';
|
|
@@ -243,10 +129,7 @@ export async function runInstallPolicy(raw) {
|
|
|
243
129
|
rpcClient = buildRpcClientFromInput(input.rpcUrl, network);
|
|
244
130
|
}
|
|
245
131
|
catch (e) {
|
|
246
|
-
return
|
|
247
|
-
ok: false,
|
|
248
|
-
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
249
|
-
};
|
|
132
|
+
return toolFailure('install_policy', e);
|
|
250
133
|
}
|
|
251
134
|
try {
|
|
252
135
|
const interpreterPolicy = input.rule.policies.find((p) => p.kind === 'interpreter');
|
|
@@ -268,10 +151,7 @@ export async function runInstallPolicy(raw) {
|
|
|
268
151
|
return { ok: true, data: result };
|
|
269
152
|
}
|
|
270
153
|
catch (e) {
|
|
271
|
-
return
|
|
272
|
-
ok: false,
|
|
273
|
-
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
274
|
-
};
|
|
154
|
+
return toolFailure('install_policy', e);
|
|
275
155
|
}
|
|
276
156
|
}
|
|
277
157
|
/** `revoke_policy` body - thin wrapper over `buildRevokePolicyXdr`.
|
|
@@ -286,10 +166,7 @@ export async function runInstallPolicy(raw) {
|
|
|
286
166
|
export async function runRevokePolicy(raw) {
|
|
287
167
|
const parsed = RevokePolicyInputSchema.safeParse(raw);
|
|
288
168
|
if (!parsed.success) {
|
|
289
|
-
return {
|
|
290
|
-
ok: false,
|
|
291
|
-
error: validationError('revoke_policy', parsed.error.issues),
|
|
292
|
-
};
|
|
169
|
+
return { ok: false, error: validationError('revoke_policy', parsed.error.issues) };
|
|
293
170
|
}
|
|
294
171
|
const input = parsed.data;
|
|
295
172
|
const network = input.network ?? 'testnet';
|
|
@@ -303,10 +180,7 @@ export async function runRevokePolicy(raw) {
|
|
|
303
180
|
rpcClient = buildRpcClientFromInput(input.rpcUrl, network);
|
|
304
181
|
}
|
|
305
182
|
catch (e) {
|
|
306
|
-
return
|
|
307
|
-
ok: false,
|
|
308
|
-
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
309
|
-
};
|
|
183
|
+
return toolFailure('revoke_policy', e);
|
|
310
184
|
}
|
|
311
185
|
try {
|
|
312
186
|
const result = await buildRevokePolicyXdr({
|
|
@@ -320,10 +194,7 @@ export async function runRevokePolicy(raw) {
|
|
|
320
194
|
return { ok: true, data: result };
|
|
321
195
|
}
|
|
322
196
|
catch (e) {
|
|
323
|
-
return
|
|
324
|
-
ok: false,
|
|
325
|
-
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
326
|
-
};
|
|
197
|
+
return toolFailure('revoke_policy', e);
|
|
327
198
|
}
|
|
328
199
|
}
|
|
329
200
|
/** `get_interpreter_info` body - thin wrapper over `getInterpreterInfo`.
|
|
@@ -345,14 +216,105 @@ export async function runRevokePolicy(raw) {
|
|
|
345
216
|
* a non-pinned `rpcUrl` would silently bind the caller to a host they
|
|
346
217
|
* picked. The pin is enforced here too, with the same `allowUnpinnedRpcUrl`
|
|
347
218
|
* opt-in as install/revoke. */
|
|
348
|
-
|
|
349
|
-
|
|
219
|
+
/** The call a predicate is evaluated against: the single top-level invocation
|
|
220
|
+
* the smart account authorises. `Policy::enforce` receives one `Context`, not
|
|
221
|
+
* a sub-invocation tree, so simulating anything deeper would claim a
|
|
222
|
+
* guarantee the contract does not make. */
|
|
223
|
+
function evalContextFromRecording(tx) {
|
|
224
|
+
const top = tx.invocations[0];
|
|
225
|
+
if (!top)
|
|
226
|
+
return null;
|
|
227
|
+
return { contract: top.contract, fn: top.fn, args: top.args };
|
|
228
|
+
}
|
|
229
|
+
/** Both `simulate_policy` and `verify_policy` evaluate against a context derived
|
|
230
|
+
* from `permitTx`; neither has anything to evaluate when the recording carries
|
|
231
|
+
* no top-level invocation. */
|
|
232
|
+
function noInvocationError(toolName) {
|
|
233
|
+
return {
|
|
234
|
+
code: TOOL_ERROR_CODE[toolName],
|
|
235
|
+
message: `${toolName}: permitTx carries no invocation to evaluate`,
|
|
236
|
+
severity: 'error',
|
|
237
|
+
retryable: false,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** `simulate_policy` body - evaluate a predicate against one recorded call.
|
|
241
|
+
*
|
|
242
|
+
* The evaluator is a second implementation of the on-chain semantics, and the
|
|
243
|
+
* conformance harness asserts it agrees with the Rust interpreter case for
|
|
244
|
+
* case. A verdict here is therefore a claim about what the contract would do,
|
|
245
|
+
* not a guess. */
|
|
246
|
+
export function runSimulatePolicy(raw) {
|
|
247
|
+
const parsed = SimulatePolicyInputSchema.safeParse(raw);
|
|
248
|
+
if (!parsed.success) {
|
|
249
|
+
return { ok: false, error: validationError('simulate_policy', parsed.error.issues) };
|
|
250
|
+
}
|
|
251
|
+
const input = parsed.data;
|
|
252
|
+
const ctx = evalContextFromRecording(input.permitTx);
|
|
253
|
+
if (!ctx)
|
|
254
|
+
return { ok: false, error: noInvocationError('simulate_policy') };
|
|
255
|
+
try {
|
|
256
|
+
const res = evaluate(input.predicate, ctx);
|
|
257
|
+
return {
|
|
258
|
+
ok: true,
|
|
259
|
+
data: {
|
|
260
|
+
permitted: res.permit,
|
|
261
|
+
reason: res.permit ? null : res.reason,
|
|
262
|
+
call: { contract: ctx.contract, fn: ctx.fn, argCount: ctx.args.length },
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
return toolFailure('simulate_policy', e);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** `verify_policy` body - the permit case plus a generated deny case per
|
|
271
|
+
* dimension.
|
|
272
|
+
*
|
|
273
|
+
* Two failure modes are being checked, and they are not the same thing. A
|
|
274
|
+
* permit case that denies means the policy is too STRICT: it would refuse the
|
|
275
|
+
* very transaction it was synthesised from. A deny case that permits means it
|
|
276
|
+
* is too LOOSE: some mutation of that transaction still gets through. `ok` is
|
|
277
|
+
* true only when neither holds. */
|
|
278
|
+
export function runVerifyPolicy(raw) {
|
|
279
|
+
const parsed = VerifyPolicyInputSchema.safeParse(raw);
|
|
350
280
|
if (!parsed.success) {
|
|
281
|
+
return { ok: false, error: validationError('verify_policy', parsed.error.issues) };
|
|
282
|
+
}
|
|
283
|
+
const input = parsed.data;
|
|
284
|
+
const ctx = evalContextFromRecording(input.permitTx);
|
|
285
|
+
if (!ctx)
|
|
286
|
+
return { ok: false, error: noInvocationError('verify_policy') };
|
|
287
|
+
try {
|
|
288
|
+
const predicate = input.predicate;
|
|
289
|
+
const cases = generateCases(predicate, ctx);
|
|
290
|
+
const permitRes = evaluate(predicate, cases.permit);
|
|
291
|
+
const denies = cases.denies.map((d) => {
|
|
292
|
+
const r = evaluate(predicate, d.ctx);
|
|
293
|
+
return {
|
|
294
|
+
dimension: d.dimension,
|
|
295
|
+
denied: !r.permit,
|
|
296
|
+
reason: r.permit ? null : r.reason,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
351
299
|
return {
|
|
352
|
-
ok:
|
|
353
|
-
|
|
300
|
+
ok: true,
|
|
301
|
+
data: {
|
|
302
|
+
ok: permitRes.permit && denies.every((d) => d.denied),
|
|
303
|
+
permit: { permitted: permitRes.permit, reason: permitRes.permit ? null : permitRes.reason },
|
|
304
|
+
denies,
|
|
305
|
+
dimensionsCovered: denies.length,
|
|
306
|
+
},
|
|
354
307
|
};
|
|
355
308
|
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
return toolFailure('verify_policy', e);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
export async function runGetInterpreterInfo(raw) {
|
|
314
|
+
const parsed = GetInterpreterInfoInputSchema.safeParse(raw);
|
|
315
|
+
if (!parsed.success) {
|
|
316
|
+
return { ok: false, error: validationError('get_interpreter_info', parsed.error.issues) };
|
|
317
|
+
}
|
|
356
318
|
const input = parsed.data;
|
|
357
319
|
const network = input.network ?? 'testnet';
|
|
358
320
|
const expectedRpc = RPC_URL_BY_NETWORK[network];
|
|
@@ -385,10 +347,7 @@ export async function runGetInterpreterInfo(raw) {
|
|
|
385
347
|
return { ok: true, data: info };
|
|
386
348
|
}
|
|
387
349
|
catch (e) {
|
|
388
|
-
return
|
|
389
|
-
ok: false,
|
|
390
|
-
error: caughtError('get_interpreter_info', 'RECORDING_FAILED', e),
|
|
391
|
-
};
|
|
350
|
+
return toolFailure('get_interpreter_info', e);
|
|
392
351
|
}
|
|
393
352
|
}
|
|
394
353
|
/** Build an InstallRpcClient from an optional URL override + selected
|
|
@@ -442,13 +401,8 @@ function enforceInterpreterPin(policies, allowUnpinned, expectedInterpreterAddre
|
|
|
442
401
|
function enforceRpcPin(toolName, rpcUrl, allowUnpinned, expectedRpc, network) {
|
|
443
402
|
if (!rpcUrl || rpcUrl === expectedRpc || allowUnpinned === true)
|
|
444
403
|
return null;
|
|
445
|
-
const code = toolName === 'install_policy'
|
|
446
|
-
? 'INSTALL_BUILD_FAILED'
|
|
447
|
-
: toolName === 'revoke_policy'
|
|
448
|
-
? 'REVOKE_BUILD_FAILED'
|
|
449
|
-
: 'RECORDING_FAILED';
|
|
450
404
|
return {
|
|
451
|
-
code,
|
|
405
|
+
code: TOOL_ERROR_CODE[toolName],
|
|
452
406
|
message: `${toolName}: rpcUrl must equal the pinned ${expectedRpc} (${network}); set allowUnpinnedRpcUrl: true to opt in to a custom endpoint`,
|
|
453
407
|
severity: 'error',
|
|
454
408
|
retryable: false,
|
|
@@ -475,6 +429,11 @@ function validationError(toolName, issues) {
|
|
|
475
429
|
* error in `details` for the agent to inspect. Exported as a test-only seam
|
|
476
430
|
* so the suite in run/index.test.ts can drive the envelope path without
|
|
477
431
|
* standing up a full recordTransaction pipeline. */
|
|
432
|
+
/** The fail-closed return every tool body uses in its `catch`. Taking only the
|
|
433
|
+
* tool name keeps the tool -> error-code mapping in TOOL_ERROR_CODE alone. */
|
|
434
|
+
function toolFailure(toolName, e) {
|
|
435
|
+
return { ok: false, error: caughtError(toolName, TOOL_ERROR_CODE[toolName], e) };
|
|
436
|
+
}
|
|
478
437
|
export function caughtError(toolName, code, e) {
|
|
479
438
|
return {
|
|
480
439
|
code,
|