@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/dist-cjs/run/index.js
CHANGED
|
@@ -18,29 +18,21 @@
|
|
|
18
18
|
// No business logic. No retries. No session state. The same call shape can
|
|
19
19
|
// drive the CLI (which calls into the same core directly without MCP).
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
21
|
+
exports.ToolErrorSchema = exports.TESTNET_RPC_URL = exports.SynthesizePolicyInputSchema = exports.RPC_URL_BY_NETWORK = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.PINNED_INTERPRETER_MAINNET_ADDRESS = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.PINNED_INTERPRETER_ADDRESS_BY_NETWORK = exports.NetworkSchema = exports.MAINNET_RPC_URL = exports.InterpreterOptionsSchema = exports.InstallPolicyInputSchema = exports.GetInterpreterInfoInputSchema = exports.ComposeUserResponsesSchema = void 0;
|
|
22
22
|
exports.runRecordTransaction = runRecordTransaction;
|
|
23
23
|
exports.runSynthesizePolicy = runSynthesizePolicy;
|
|
24
|
-
exports.runSimulatePolicy = runSimulatePolicy;
|
|
25
|
-
exports.runVerifyPolicy = runVerifyPolicy;
|
|
26
24
|
exports.runInstallPolicy = runInstallPolicy;
|
|
27
|
-
exports.enforceAuthorityScan = enforceAuthorityScan;
|
|
28
|
-
exports.runMergePolicy = runMergePolicy;
|
|
29
25
|
exports.runRevokePolicy = runRevokePolicy;
|
|
26
|
+
exports.runSimulatePolicy = runSimulatePolicy;
|
|
27
|
+
exports.runVerifyPolicy = runVerifyPolicy;
|
|
30
28
|
exports.runGetInterpreterInfo = runGetInterpreterInfo;
|
|
31
29
|
exports.caughtError = caughtError;
|
|
32
30
|
const node_crypto_1 = require("node:crypto");
|
|
33
31
|
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
34
32
|
const index_ts_1 = require("../index.js");
|
|
35
|
-
const authority_overlap_ts_1 = require("../install/authority-overlap.js");
|
|
36
33
|
const build_install_policy_ts_1 = require("../install/build-install-policy.js");
|
|
37
|
-
const build_merge_policy_ts_1 = require("../install/build-merge-policy.js");
|
|
38
34
|
const get_interpreter_info_ts_1 = require("../install/get-interpreter-info.js");
|
|
39
|
-
const
|
|
40
|
-
const read_account_rules_ts_1 = require("../install/read-account-rules.js");
|
|
41
|
-
const decode_ts_1 = require("../predicate/decode.js");
|
|
42
|
-
const encode_ts_1 = require("../predicate/encode.js");
|
|
43
|
-
const index_ts_2 = require("../verify/index.js");
|
|
35
|
+
const index_ts_2 = require("../simulate/index.js");
|
|
44
36
|
const schemas_ts_1 = require("./schemas.js");
|
|
45
37
|
// Re-export the underlying Zod schemas so the MCP package (and any other
|
|
46
38
|
// downstream consumer) can import the canonical input shapes from the same
|
|
@@ -52,26 +44,21 @@ Object.defineProperty(exports, "GetInterpreterInfoInputSchema", { enumerable: tr
|
|
|
52
44
|
Object.defineProperty(exports, "InstallPolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.InstallPolicyInputSchema; } });
|
|
53
45
|
Object.defineProperty(exports, "InterpreterOptionsSchema", { enumerable: true, get: function () { return schemas_ts_2.InterpreterOptionsSchema; } });
|
|
54
46
|
Object.defineProperty(exports, "MAINNET_RPC_URL", { enumerable: true, get: function () { return schemas_ts_2.MAINNET_RPC_URL; } });
|
|
55
|
-
Object.defineProperty(exports, "MandateSpecSchema", { enumerable: true, get: function () { return schemas_ts_2.MandateSpecSchema; } });
|
|
56
47
|
Object.defineProperty(exports, "NetworkSchema", { enumerable: true, get: function () { return schemas_ts_2.NetworkSchema; } });
|
|
57
|
-
Object.defineProperty(exports, "OraclePriceFixtureSchema", { enumerable: true, get: function () { return schemas_ts_2.OraclePriceFixtureSchema; } });
|
|
58
|
-
Object.defineProperty(exports, "OzAdapterConfigSchema", { enumerable: true, get: function () { return schemas_ts_2.OzAdapterConfigSchema; } });
|
|
59
48
|
Object.defineProperty(exports, "PINNED_INTERPRETER_ADDRESS_BY_NETWORK", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_ADDRESS_BY_NETWORK; } });
|
|
60
49
|
Object.defineProperty(exports, "PINNED_INTERPRETER_GRAMMAR_VERSION", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_GRAMMAR_VERSION; } });
|
|
61
50
|
Object.defineProperty(exports, "PINNED_INTERPRETER_MAINNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_MAINNET_ADDRESS; } });
|
|
62
51
|
Object.defineProperty(exports, "PINNED_INTERPRETER_TESTNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_TESTNET_ADDRESS; } });
|
|
63
|
-
Object.defineProperty(exports, "
|
|
52
|
+
Object.defineProperty(exports, "PINNED_INTERPRETER_WASM_SHA256", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_WASM_SHA256; } });
|
|
64
53
|
Object.defineProperty(exports, "PredicateLeafSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateLeafSchema; } });
|
|
65
54
|
Object.defineProperty(exports, "PredicateNodeSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateNodeSchema; } });
|
|
66
55
|
Object.defineProperty(exports, "RecordedTransactionSchema", { enumerable: true, get: function () { return schemas_ts_2.RecordedTransactionSchema; } });
|
|
67
56
|
Object.defineProperty(exports, "RecordTransactionInputSchema", { enumerable: true, get: function () { return schemas_ts_2.RecordTransactionInputSchema; } });
|
|
68
57
|
Object.defineProperty(exports, "RevokePolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.RevokePolicyInputSchema; } });
|
|
69
58
|
Object.defineProperty(exports, "RPC_URL_BY_NETWORK", { enumerable: true, get: function () { return schemas_ts_2.RPC_URL_BY_NETWORK; } });
|
|
70
|
-
Object.defineProperty(exports, "SimulatePolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.SimulatePolicyInputSchema; } });
|
|
71
59
|
Object.defineProperty(exports, "SynthesizePolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.SynthesizePolicyInputSchema; } });
|
|
72
60
|
Object.defineProperty(exports, "TESTNET_RPC_URL", { enumerable: true, get: function () { return schemas_ts_2.TESTNET_RPC_URL; } });
|
|
73
61
|
Object.defineProperty(exports, "ToolErrorSchema", { enumerable: true, get: function () { return schemas_ts_2.ToolErrorSchema; } });
|
|
74
|
-
Object.defineProperty(exports, "VerifyPolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.VerifyPolicyInputSchema; } });
|
|
75
62
|
/** Map every tool name to its canonical domain error code. Replaces a 7-way
|
|
76
63
|
* if/else so adding a tool adds one line here rather than a new branch in
|
|
77
64
|
* each envelope call. */
|
|
@@ -98,10 +85,7 @@ const TOOL_ERROR_CODE = {
|
|
|
98
85
|
async function runRecordTransaction(raw) {
|
|
99
86
|
const parsed = schemas_ts_1.RecordTransactionInputSchema.safeParse(raw);
|
|
100
87
|
if (!parsed.success) {
|
|
101
|
-
return {
|
|
102
|
-
ok: false,
|
|
103
|
-
error: validationError('record_transaction', parsed.error.issues),
|
|
104
|
-
};
|
|
88
|
+
return { ok: false, error: validationError('record_transaction', parsed.error.issues) };
|
|
105
89
|
}
|
|
106
90
|
const input = parsed.data;
|
|
107
91
|
// Strip the wire-only `confidenceOverride` and pass the rest straight through.
|
|
@@ -117,14 +101,10 @@ async function runRecordTransaction(raw) {
|
|
|
117
101
|
return await (0, index_ts_1.recordTransaction)(coreInput);
|
|
118
102
|
}
|
|
119
103
|
catch (e) {
|
|
120
|
-
return
|
|
121
|
-
ok: false,
|
|
122
|
-
error: caughtError('record_transaction', 'RECORDING_FAILED', e),
|
|
123
|
-
};
|
|
104
|
+
return toolFailure('record_transaction', e);
|
|
124
105
|
}
|
|
125
106
|
}
|
|
126
107
|
/** `synthesize_policy` body - discriminated union on `source`:
|
|
127
|
-
* - `mandate` -> synthesizeFromMandate
|
|
128
108
|
* - `recording` -> synthesizeFromRecording
|
|
129
109
|
* Exposing BOTH front-ends through ONE tool keeps the MCP surface tiny while
|
|
130
110
|
* letting the agent pick the deterministic or the inferred path. The CLI
|
|
@@ -138,21 +118,10 @@ async function runRecordTransaction(raw) {
|
|
|
138
118
|
async function runSynthesizePolicy(raw) {
|
|
139
119
|
const parsed = schemas_ts_1.SynthesizePolicyInputSchema.safeParse(raw);
|
|
140
120
|
if (!parsed.success) {
|
|
141
|
-
return {
|
|
142
|
-
ok: false,
|
|
143
|
-
error: validationError('synthesize_policy', parsed.error.issues),
|
|
144
|
-
};
|
|
121
|
+
return { ok: false, error: validationError('synthesize_policy', parsed.error.issues) };
|
|
145
122
|
}
|
|
146
123
|
const input = parsed.data;
|
|
147
|
-
const ozConfig = resolveOzConfig(input);
|
|
148
124
|
try {
|
|
149
|
-
if (input.source === 'mandate') {
|
|
150
|
-
// Zod's optional fields widen to `T | undefined`, which the core's
|
|
151
|
-
// exact-optional MandateSpec rejects; the schema already validated the
|
|
152
|
-
// shape, so assert it (same pattern as the recordedTx cast below).
|
|
153
|
-
return await (0, index_ts_1.synthesizeFromMandate)(input.mandate, ozConfig, input.explain === true ? { explain: true } : {});
|
|
154
|
-
}
|
|
155
|
-
// recording source
|
|
156
125
|
const recorded = input.recordedTx;
|
|
157
126
|
return await (0, index_ts_1.synthesizeFromRecording)(recorded, {
|
|
158
127
|
network: input.network,
|
|
@@ -162,112 +131,16 @@ async function runSynthesizePolicy(raw) {
|
|
|
162
131
|
: {}),
|
|
163
132
|
...(input.interpreter !== undefined ? { interpreter: input.interpreter } : {}),
|
|
164
133
|
...(input.explain === true ? { explain: true } : {}),
|
|
165
|
-
}, ozConfig);
|
|
166
|
-
}
|
|
167
|
-
catch (e) {
|
|
168
|
-
return {
|
|
169
|
-
ok: false,
|
|
170
|
-
error: caughtError('synthesize_policy', 'SYNTHESIS_ERROR', e),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function resolveOzConfig(input) {
|
|
175
|
-
if (input.ozConfig)
|
|
176
|
-
return input.ozConfig;
|
|
177
|
-
// The mandate path is network-agnostic; fall back to mainnet so the
|
|
178
|
-
// placeholder OZ instance addresses are deterministic.
|
|
179
|
-
return (0, index_ts_1.placeholderOzConfig)('mainnet');
|
|
180
|
-
}
|
|
181
|
-
/** `simulate_policy` body - thin wrapper over `simulatePolicy`. The engine
|
|
182
|
-
* already returns fail-closed `{ok:false, error}` for runtime failures
|
|
183
|
-
* (SIMULATION_ERROR), so the try/catch envelope is for raw SDK throws
|
|
184
|
-
* only - same pattern as the other two wrappers. The predicate is
|
|
185
|
-
* passed inline (stateless by design; no `proposed_policy_id` lookup). */
|
|
186
|
-
async function runSimulatePolicy(raw) {
|
|
187
|
-
const parsed = schemas_ts_1.SimulatePolicyInputSchema.safeParse(raw);
|
|
188
|
-
if (!parsed.success) {
|
|
189
|
-
return {
|
|
190
|
-
ok: false,
|
|
191
|
-
error: validationError('simulate_policy', parsed.error.issues),
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
const input = parsed.data;
|
|
195
|
-
try {
|
|
196
|
-
// The recursive PredicateNodeSchema + ContractInvocationSchema are
|
|
197
|
-
// typed `z.ZodType<unknown>` to survive TS's circular inference; the
|
|
198
|
-
// engine wants typed `PredicateNode | null` + `RecordedTransaction`.
|
|
199
|
-
// The schema already validated the shape, so assert through the
|
|
200
|
-
// unknown back to the core types. Same pattern as the recordedTx
|
|
201
|
-
// cast in `runSynthesizePolicy`.
|
|
202
|
-
return (0, index_ts_2.simulatePolicy)(input.predicate, input.permitTx, {
|
|
203
|
-
...(input.validUntilLedger !== undefined
|
|
204
|
-
? { validUntilLedger: input.validUntilLedger }
|
|
205
|
-
: {}),
|
|
206
|
-
...(input.oraclePricesByAsset !== undefined
|
|
207
|
-
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
208
|
-
: {}),
|
|
209
134
|
});
|
|
210
135
|
}
|
|
211
136
|
catch (e) {
|
|
212
|
-
return
|
|
213
|
-
ok: false,
|
|
214
|
-
error: caughtError('simulate_policy', 'SIMULATION_ERROR', e),
|
|
215
|
-
};
|
|
137
|
+
return toolFailure('synthesize_policy', e);
|
|
216
138
|
}
|
|
217
139
|
}
|
|
218
|
-
/** `verify_policy` body - thin wrapper over `verifyPolicy`. The engine
|
|
219
|
-
* already returns `{ok:false, error}` with code VERIFICATION_FAILED when
|
|
220
|
-
* the minimality check fails; the try/catch envelope is for raw SDK
|
|
221
|
-
* throws only. Mirrors `runSimulatePolicy` exactly. */
|
|
222
|
-
async function runVerifyPolicy(raw) {
|
|
223
|
-
const parsed = schemas_ts_1.VerifyPolicyInputSchema.safeParse(raw);
|
|
224
|
-
if (!parsed.success) {
|
|
225
|
-
return {
|
|
226
|
-
ok: false,
|
|
227
|
-
error: validationError('verify_policy', parsed.error.issues),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
const input = parsed.data;
|
|
231
|
-
try {
|
|
232
|
-
// Same cast as runSimulatePolicy: the recursive schemas are typed
|
|
233
|
-
// `unknown`; the engine wants typed `PredicateNode` +
|
|
234
|
-
// `RecordedTransaction`. The schema already validated the shape.
|
|
235
|
-
return (0, index_ts_2.verifyPolicy)(input.predicate, input.permitTx, {
|
|
236
|
-
...(input.validUntilLedger !== undefined ? { validUntilLedger: input.validUntilLedger } : {}),
|
|
237
|
-
...(input.oraclePricesByAsset !== undefined
|
|
238
|
-
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
239
|
-
: {}),
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
catch (e) {
|
|
243
|
-
return {
|
|
244
|
-
ok: false,
|
|
245
|
-
error: caughtError('verify_policy', 'VERIFICATION_FAILED', e),
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
/** `install_policy` body - thin wrapper over `buildInstallPolicyXdr`.
|
|
250
|
-
* Returns the unsigned Soroban transaction envelope (base64 XDR) the
|
|
251
|
-
* wallet signs. The wallet signature IS the user-confirmation step - no
|
|
252
|
-
* `action_id` two-call pair (the server is stateless, see server.ts:10-12).
|
|
253
|
-
* One call installs the policy outright: `add_context_rule` carries the
|
|
254
|
-
* predicate to the interpreter in its `policies` install_param, so no
|
|
255
|
-
* separate `interpreter.install` call is needed or possible.
|
|
256
|
-
*
|
|
257
|
-
* Default-deny: an interpreter policy address other than the pinned
|
|
258
|
-
* interpreter for the selected network is REFUSED (the smart account
|
|
259
|
-
* would delegate to an interpreter the caller controls); the same
|
|
260
|
-
* applies to a non-pinned RPC URL (the auth nonce the wallet signs
|
|
261
|
-
* comes from the RPC). Both gates accept an explicit opt-in flag.
|
|
262
|
-
* Pin selection follows `input.network` (defaults to `testnet` so the
|
|
263
|
-
* pre-mainnet callers keep working unchanged). */
|
|
264
140
|
async function runInstallPolicy(raw) {
|
|
265
141
|
const parsed = schemas_ts_1.InstallPolicyInputSchema.safeParse(raw);
|
|
266
142
|
if (!parsed.success) {
|
|
267
|
-
return {
|
|
268
|
-
ok: false,
|
|
269
|
-
error: validationError('install_policy', parsed.error.issues),
|
|
270
|
-
};
|
|
143
|
+
return { ok: false, error: validationError('install_policy', parsed.error.issues) };
|
|
271
144
|
}
|
|
272
145
|
const input = parsed.data;
|
|
273
146
|
const network = input.network ?? 'testnet';
|
|
@@ -287,10 +160,7 @@ async function runInstallPolicy(raw) {
|
|
|
287
160
|
rpcClient = buildRpcClientFromInput(input.rpcUrl, network);
|
|
288
161
|
}
|
|
289
162
|
catch (e) {
|
|
290
|
-
return
|
|
291
|
-
ok: false,
|
|
292
|
-
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
293
|
-
};
|
|
163
|
+
return toolFailure('install_policy', e);
|
|
294
164
|
}
|
|
295
165
|
try {
|
|
296
166
|
const interpreterPolicy = input.rule.policies.find((p) => p.kind === 'interpreter');
|
|
@@ -298,25 +168,6 @@ async function runInstallPolicy(raw) {
|
|
|
298
168
|
const predicateHash = (0, node_crypto_1.createHash)('sha256')
|
|
299
169
|
.update(Buffer.from(encodedPredicate, 'base64'))
|
|
300
170
|
.digest('hex');
|
|
301
|
-
// ---- Cross-rule authority scan ----
|
|
302
|
-
// OZ enforces only the policies of the rule the caller names, so a signer
|
|
303
|
-
// who also sits in a wider rule keeps that wider authority no matter what
|
|
304
|
-
// this predicate says. Refuse by default when the wider rule has no policy
|
|
305
|
-
// at all, because that makes this install decorative.
|
|
306
|
-
const authorityScan = input.skipAuthorityScan
|
|
307
|
-
? { ran: false, skipped: true, reason: 'skipped at caller request', overlaps: [] }
|
|
308
|
-
: await scanAuthorityOverlap({
|
|
309
|
-
smartAccount: input.smartAccount,
|
|
310
|
-
interpreterAddress: expectedInterpreter,
|
|
311
|
-
rule: input.rule,
|
|
312
|
-
encodedPredicate,
|
|
313
|
-
rpcUrl: input.rpcUrl ?? expectedRpc,
|
|
314
|
-
network,
|
|
315
|
-
});
|
|
316
|
-
const overlapError = enforceAuthorityScan(authorityScan, input.allowAuthorityOverlap);
|
|
317
|
-
if (overlapError) {
|
|
318
|
-
return { ok: false, error: overlapError };
|
|
319
|
-
}
|
|
320
171
|
const result = await (0, build_install_policy_ts_1.buildInstallPolicyXdr)({
|
|
321
172
|
smartAccount: input.smartAccount,
|
|
322
173
|
sourceAccount: input.sourceAccount,
|
|
@@ -328,259 +179,10 @@ async function runInstallPolicy(raw) {
|
|
|
328
179
|
rpc: rpcClient,
|
|
329
180
|
...(input.baseFee !== undefined ? { baseFee: input.baseFee } : {}),
|
|
330
181
|
});
|
|
331
|
-
return { ok: true, data:
|
|
332
|
-
}
|
|
333
|
-
catch (e) {
|
|
334
|
-
return {
|
|
335
|
-
ok: false,
|
|
336
|
-
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
/** Default-deny on the cross-rule scan.
|
|
341
|
-
*
|
|
342
|
-
* Refuses whenever the scan cannot establish that this policy binds the calls
|
|
343
|
-
* it names. An unpoliced neighbour provably does not constrain them. An
|
|
344
|
-
* opaque one, policed by a contract this tool cannot decode, is not KNOWN to,
|
|
345
|
-
* and "not known to" is not "safe" - the same posture as the interpreter and
|
|
346
|
-
* RPC pins. An incomplete scan is refused for the same reason: the overlap
|
|
347
|
-
* list is then a subset of the account, so an empty list proves nothing.
|
|
348
|
-
*
|
|
349
|
-
* `not-restricting` is reported but does NOT block. Both rules are ours and
|
|
350
|
-
* both constrain the calls, and the conjunction remedy is offered; refusing
|
|
351
|
-
* there would also block the legitimate act of adding a separate capability,
|
|
352
|
-
* which OZ composes correctly as a union.
|
|
353
|
-
*
|
|
354
|
-
* Returns a ToolError or null, matching `enforceInterpreterPin`. */
|
|
355
|
-
function enforceAuthorityScan(scan, allowOverlap) {
|
|
356
|
-
if (!scan || allowOverlap === true)
|
|
357
|
-
return null;
|
|
358
|
-
// Default-deny by exclusion rather than by enumeration: anything that is not
|
|
359
|
-
// the one known-safe severity blocks. Listing the blocking severities
|
|
360
|
-
// instead would mean a severity added later silently passes until someone
|
|
361
|
-
// remembers to add it here, and the safe direction is the opposite.
|
|
362
|
-
const blocking = scan.overlaps.filter((o) => o.severity !== 'not-restricting');
|
|
363
|
-
const unpoliced = blocking.filter((o) => o.severity === 'bypass').map((o) => o.ruleId);
|
|
364
|
-
const opaque = blocking.filter((o) => o.severity === 'unknown').map((o) => o.ruleId);
|
|
365
|
-
const unrecognised = blocking
|
|
366
|
-
.filter((o) => o.severity !== 'bypass' && o.severity !== 'unknown')
|
|
367
|
-
.map((o) => o.ruleId);
|
|
368
|
-
if (blocking.length > 0) {
|
|
369
|
-
const parts = [
|
|
370
|
-
unpoliced.length > 0 ? `rule ${unpoliced.join(', ')} has no policy attached` : '',
|
|
371
|
-
opaque.length > 0
|
|
372
|
-
? `rule ${opaque.join(', ')} is policed by a contract this tool cannot decode`
|
|
373
|
-
: '',
|
|
374
|
-
unrecognised.length > 0
|
|
375
|
-
? `rule ${unrecognised.join(', ')} carries an overlap this build does not recognise`
|
|
376
|
-
: '',
|
|
377
|
-
].filter(Boolean);
|
|
378
|
-
return {
|
|
379
|
-
code: 'INSTALL_BUILD_FAILED',
|
|
380
|
-
message: `install_policy: a signer of this rule can already make the same calls through another context rule, so this policy is not established to restrict them: ${parts.join('; ')}. Remove the shared signer from that rule, attach a policy this tool can read, or set allowAuthorityOverlap: true to install anyway`,
|
|
381
|
-
severity: 'error',
|
|
382
|
-
retryable: false,
|
|
383
|
-
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
// A scan that was meant to run and threw tells us nothing. Reporting it in
|
|
387
|
-
// a string field and proceeding is the same fail-open shape as an incomplete
|
|
388
|
-
// scan, and a caller that does not read `ran` cannot tell it from a clean
|
|
389
|
-
// result.
|
|
390
|
-
if (scan.ran === false && scan.skipped !== true) {
|
|
391
|
-
return {
|
|
392
|
-
code: 'INSTALL_BUILD_FAILED',
|
|
393
|
-
message: `install_policy: the cross-rule authority scan could not run (${scan.reason ?? 'unknown error'}), so it cannot establish that this policy restricts anything; retry, or set allowAuthorityOverlap: true to install without that assurance`,
|
|
394
|
-
severity: 'error',
|
|
395
|
-
retryable: true,
|
|
396
|
-
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
if (scan.incomplete === true) {
|
|
400
|
-
return {
|
|
401
|
-
code: 'INSTALL_BUILD_FAILED',
|
|
402
|
-
message: 'install_policy: the account has more context rules than the scan could account for, so the overlap result is incomplete and cannot establish that this policy restricts anything; set allowAuthorityOverlap: true to install without that assurance',
|
|
403
|
-
severity: 'error',
|
|
404
|
-
retryable: false,
|
|
405
|
-
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
return null;
|
|
409
|
-
}
|
|
410
|
-
/** Read the account's other context rules and report where this install's
|
|
411
|
-
* signers already hold authority over the same calls.
|
|
412
|
-
*
|
|
413
|
-
* A failure to read is reported rather than thrown, and the caller-facing
|
|
414
|
-
* decision is made by `enforceAuthorityScan`: a scan that tried and failed
|
|
415
|
-
* tells us nothing, so it refuses rather than passing as a clean account.
|
|
416
|
-
*
|
|
417
|
-
* The account data this trusts comes from whichever RPC answered, so a
|
|
418
|
-
* hostile RPC could describe an account with no overlapping rules. That is
|
|
419
|
-
* bounded by the pin already enforced above: `enforceRpcPin` returns before
|
|
420
|
-
* this runs, so the URL is the pinned one for the network unless the caller
|
|
421
|
-
* explicitly set `allowUnpinnedRpcUrl`. This scan deliberately does not add a
|
|
422
|
-
* second pin check, because two places deciding the same thing drift. */
|
|
423
|
-
async function scanAuthorityOverlap(args) {
|
|
424
|
-
try {
|
|
425
|
-
if (!args.encodedPredicate) {
|
|
426
|
-
// Not applicable rather than failed: this rule installs no interpreter
|
|
427
|
-
// predicate, so there is nothing of ours for another rule to undercut.
|
|
428
|
-
// Returning undefined keeps it out of the refusal path, which is
|
|
429
|
-
// reserved for scans that were meant to run and could not.
|
|
430
|
-
return undefined;
|
|
431
|
-
}
|
|
432
|
-
const server = new stellar_sdk_1.rpc.Server(args.rpcUrl, { allowHttp: false });
|
|
433
|
-
const reader = (0, read_account_rules_ts_1.accountRuleReaderFromServer)(server, schemas_ts_1.NETWORK_PASSPHRASES[args.network]);
|
|
434
|
-
const collected = await (0, read_account_rules_ts_1.collectObservedRules)({
|
|
435
|
-
reader,
|
|
436
|
-
smartAccount: args.smartAccount,
|
|
437
|
-
interpreterAddress: args.interpreterAddress,
|
|
438
|
-
});
|
|
439
|
-
const overlaps = (0, authority_overlap_ts_1.findAuthorityOverlaps)({
|
|
440
|
-
intended: {
|
|
441
|
-
// `add_context_rule` always creates a NEW rule, so there is no id to
|
|
442
|
-
// exclude; -1 matches nothing on the account.
|
|
443
|
-
ruleId: -1,
|
|
444
|
-
contextType: toOverlapContextType(args.rule.contextRuleType),
|
|
445
|
-
signers: args.rule.signers,
|
|
446
|
-
predicate: (0, decode_ts_1.decodePredicate)(args.encodedPredicate),
|
|
447
|
-
},
|
|
448
|
-
existing: collected.rules,
|
|
449
|
-
});
|
|
450
|
-
return { ran: true, incomplete: collected.incomplete, overlaps };
|
|
451
|
-
}
|
|
452
|
-
catch (e) {
|
|
453
|
-
return {
|
|
454
|
-
ran: false,
|
|
455
|
-
reason: e instanceof Error ? e.message : String(e),
|
|
456
|
-
overlaps: [],
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
/** The rule draft names the callee `contract`; the analyser calls it
|
|
461
|
-
* `address`. Same value, two vocabularies. */
|
|
462
|
-
function toOverlapContextType(ct) {
|
|
463
|
-
switch (ct.kind) {
|
|
464
|
-
case 'call_contract':
|
|
465
|
-
return { kind: 'call_contract', address: ct.contract };
|
|
466
|
-
case 'create_contract':
|
|
467
|
-
return { kind: 'create_contract', wasmHash: ct.wasmHash };
|
|
468
|
-
default:
|
|
469
|
-
return { kind: 'default' };
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
/** `merge_policy` body - the tightening remedy for a cross-rule overlap.
|
|
473
|
-
*
|
|
474
|
-
* Replaces a rule's predicate with the conjunction of it and a new one. This
|
|
475
|
-
* is the action `install_policy` recommends when it reports an overlap
|
|
476
|
-
* between two rules our interpreter polices, and it is deliberately NOT
|
|
477
|
-
* something `install_policy` does on its own: it detaches a live policy, so
|
|
478
|
-
* the operator has to ask for it.
|
|
479
|
-
*
|
|
480
|
-
* Two transactions in order. `add_policy` refuses a policy already on the
|
|
481
|
-
* rule, so the old attachment goes first, and the second transaction cannot
|
|
482
|
-
* be simulated until the first confirms. */
|
|
483
|
-
async function runMergePolicy(raw) {
|
|
484
|
-
const parsed = schemas_ts_1.MergePolicyInputSchema.safeParse(raw);
|
|
485
|
-
if (!parsed.success) {
|
|
486
|
-
return { ok: false, error: validationError('install_policy', parsed.error.issues) };
|
|
487
|
-
}
|
|
488
|
-
const input = parsed.data;
|
|
489
|
-
const network = input.network ?? 'testnet';
|
|
490
|
-
const expectedInterpreter = schemas_ts_1.PINNED_INTERPRETER_ADDRESS_BY_NETWORK[network];
|
|
491
|
-
const expectedRpc = schemas_ts_1.RPC_URL_BY_NETWORK[network];
|
|
492
|
-
const rpcPinningError = enforceRpcPin('install_policy', input.rpcUrl, input.allowUnpinnedRpcUrl, expectedRpc, network);
|
|
493
|
-
if (rpcPinningError)
|
|
494
|
-
return { ok: false, error: rpcPinningError };
|
|
495
|
-
try {
|
|
496
|
-
const rpcUrl = input.rpcUrl ?? expectedRpc;
|
|
497
|
-
const server = new stellar_sdk_1.rpc.Server(rpcUrl, { allowHttp: false });
|
|
498
|
-
const reader = (0, read_account_rules_ts_1.accountRuleReaderFromServer)(server, schemas_ts_1.NETWORK_PASSPHRASES[network]);
|
|
499
|
-
const collected = await (0, read_account_rules_ts_1.collectObservedRules)({
|
|
500
|
-
reader,
|
|
501
|
-
smartAccount: input.smartAccount,
|
|
502
|
-
interpreterAddress: expectedInterpreter,
|
|
503
|
-
});
|
|
504
|
-
const rule = collected.rules.find((r) => r.id === input.ruleId);
|
|
505
|
-
if (!rule) {
|
|
506
|
-
return {
|
|
507
|
-
ok: false,
|
|
508
|
-
error: {
|
|
509
|
-
code: 'INSTALL_BUILD_FAILED',
|
|
510
|
-
message: `merge_policy: rule ${input.ruleId} was not found on ${input.smartAccount}${collected.incomplete
|
|
511
|
-
? ' (the rule scan was incomplete, so it may exist but was not reached)'
|
|
512
|
-
: ''}`,
|
|
513
|
-
severity: 'error',
|
|
514
|
-
retryable: false,
|
|
515
|
-
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
516
|
-
},
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
const plan = (0, plan_merge_policy_ts_1.planMergePolicy)({
|
|
520
|
-
rule,
|
|
521
|
-
interpreterAddress: expectedInterpreter,
|
|
522
|
-
incoming: (0, decode_ts_1.decodePredicate)(input.incomingPredicateBlobBase64),
|
|
523
|
-
step: input.step,
|
|
524
|
-
});
|
|
525
|
-
if (!plan.ok) {
|
|
526
|
-
return {
|
|
527
|
-
ok: false,
|
|
528
|
-
error: {
|
|
529
|
-
code: 'INSTALL_BUILD_FAILED',
|
|
530
|
-
message: `merge_policy: ${plan.reason}`,
|
|
531
|
-
severity: 'error',
|
|
532
|
-
retryable: false,
|
|
533
|
-
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
534
|
-
},
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
const encoded = (0, encode_ts_1.encodePredicate)(plan.predicate);
|
|
538
|
-
// The nonce is read, not assumed. OZ's `remove_policy` discards the result
|
|
539
|
-
// of `try_uninstall`, so a detach whose uninstall panicked - our
|
|
540
|
-
// `uninstall` panics MissingState when the master set has been archived -
|
|
541
|
-
// detaches the policy while leaving our nonce behind. Re-installing at 1
|
|
542
|
-
// would then be refused as a replay and the rule would sit unpoliced.
|
|
543
|
-
let installNonce = 1;
|
|
544
|
-
const nonceWarnings = [];
|
|
545
|
-
if (input.step === 'reinstall') {
|
|
546
|
-
const entries = await server.getLedgerEntries((0, read_account_rules_ts_1.nonceLedgerKey)(expectedInterpreter, input.smartAccount, input.ruleId));
|
|
547
|
-
const raw = entries.entries?.[0]?.val;
|
|
548
|
-
const stored = raw && raw.switch() === stellar_sdk_1.xdr.LedgerEntryType.contractData()
|
|
549
|
-
? raw.contractData().val()
|
|
550
|
-
: undefined;
|
|
551
|
-
if (stored && stored.switch() === stellar_sdk_1.xdr.ScValType.scvU32()) {
|
|
552
|
-
installNonce = stored.u32() + 1;
|
|
553
|
-
nonceWarnings.push(`the previous uninstall did not complete: rule ${input.ruleId} still holds interpreter state at nonce ${stored.u32()}, so this reinstalls at ${installNonce} rather than 1. The rule's counters were NOT reset.`);
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
const built = await (0, build_merge_policy_ts_1.buildMergePolicyXdr)({
|
|
557
|
-
smartAccount: input.smartAccount,
|
|
558
|
-
sourceAccount: input.sourceAccount,
|
|
559
|
-
networkPassphrase: schemas_ts_1.NETWORK_PASSPHRASES[network],
|
|
560
|
-
ruleId: input.ruleId,
|
|
561
|
-
policyId: plan.policyId,
|
|
562
|
-
interpreterAddress: expectedInterpreter,
|
|
563
|
-
step: input.step,
|
|
564
|
-
encodedPredicate: encoded.encodedPredicate,
|
|
565
|
-
predicateHash: encoded.predicateHash,
|
|
566
|
-
installNonce,
|
|
567
|
-
...(plan.oracleParams ? { oracleParams: plan.oracleParams } : {}),
|
|
568
|
-
rpc: (0, build_install_policy_ts_1.rpcClientFromServer)(server, schemas_ts_1.NETWORK_PASSPHRASES[network]),
|
|
569
|
-
...(input.baseFee !== undefined ? { baseFee: input.baseFee } : {}),
|
|
570
|
-
});
|
|
571
|
-
return {
|
|
572
|
-
ok: true,
|
|
573
|
-
data: {
|
|
574
|
-
...built,
|
|
575
|
-
mergedPredicateHash: encoded.predicateHash,
|
|
576
|
-
mergedPredicateBlobBase64: encoded.encodedPredicate,
|
|
577
|
-
warnings: [...plan.warnings, ...nonceWarnings],
|
|
578
|
-
followUp: plan.followUp,
|
|
579
|
-
},
|
|
580
|
-
};
|
|
182
|
+
return { ok: true, data: result };
|
|
581
183
|
}
|
|
582
184
|
catch (e) {
|
|
583
|
-
return
|
|
185
|
+
return toolFailure('install_policy', e);
|
|
584
186
|
}
|
|
585
187
|
}
|
|
586
188
|
/** `revoke_policy` body - thin wrapper over `buildRevokePolicyXdr`.
|
|
@@ -595,10 +197,7 @@ async function runMergePolicy(raw) {
|
|
|
595
197
|
async function runRevokePolicy(raw) {
|
|
596
198
|
const parsed = schemas_ts_1.RevokePolicyInputSchema.safeParse(raw);
|
|
597
199
|
if (!parsed.success) {
|
|
598
|
-
return {
|
|
599
|
-
ok: false,
|
|
600
|
-
error: validationError('revoke_policy', parsed.error.issues),
|
|
601
|
-
};
|
|
200
|
+
return { ok: false, error: validationError('revoke_policy', parsed.error.issues) };
|
|
602
201
|
}
|
|
603
202
|
const input = parsed.data;
|
|
604
203
|
const network = input.network ?? 'testnet';
|
|
@@ -612,10 +211,7 @@ async function runRevokePolicy(raw) {
|
|
|
612
211
|
rpcClient = buildRpcClientFromInput(input.rpcUrl, network);
|
|
613
212
|
}
|
|
614
213
|
catch (e) {
|
|
615
|
-
return
|
|
616
|
-
ok: false,
|
|
617
|
-
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
618
|
-
};
|
|
214
|
+
return toolFailure('revoke_policy', e);
|
|
619
215
|
}
|
|
620
216
|
try {
|
|
621
217
|
const result = await (0, build_install_policy_ts_1.buildRevokePolicyXdr)({
|
|
@@ -629,10 +225,7 @@ async function runRevokePolicy(raw) {
|
|
|
629
225
|
return { ok: true, data: result };
|
|
630
226
|
}
|
|
631
227
|
catch (e) {
|
|
632
|
-
return
|
|
633
|
-
ok: false,
|
|
634
|
-
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
635
|
-
};
|
|
228
|
+
return toolFailure('revoke_policy', e);
|
|
636
229
|
}
|
|
637
230
|
}
|
|
638
231
|
/** `get_interpreter_info` body - thin wrapper over `getInterpreterInfo`.
|
|
@@ -642,26 +235,117 @@ async function runRevokePolicy(raw) {
|
|
|
642
235
|
* fabricating it would be a lie on a security surface; the live
|
|
643
236
|
* mismatch check is worth MORE).
|
|
644
237
|
*
|
|
645
|
-
* Network-aware: `input.network` selects
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
* writing.
|
|
238
|
+
* Network-aware: `input.network` selects which interpreter pin and RPC
|
|
239
|
+
* to use. Mainnet was rolled out 2026-08-04 - the same wasm hash was
|
|
240
|
+
* uploaded to mainnet as was exercised on testnet, so a single
|
|
241
|
+
* `PINNED_INTERPRETER_WASM_SHA256` constant backs both networks.
|
|
242
|
+
* The address differs because instance ids are network-scoped.
|
|
243
|
+
* UNAUDITED at the time of writing.
|
|
651
244
|
*
|
|
652
245
|
* Same RPC pin as install/revoke: when `verifyLive` triggers an outbound
|
|
653
246
|
* call, the auth-digest + the answer bind to whichever RPC answered, so
|
|
654
247
|
* a non-pinned `rpcUrl` would silently bind the caller to a host they
|
|
655
248
|
* picked. The pin is enforced here too, with the same `allowUnpinnedRpcUrl`
|
|
656
249
|
* opt-in as install/revoke. */
|
|
657
|
-
|
|
658
|
-
|
|
250
|
+
/** The call a predicate is evaluated against: the single top-level invocation
|
|
251
|
+
* the smart account authorises. `Policy::enforce` receives one `Context`, not
|
|
252
|
+
* a sub-invocation tree, so simulating anything deeper would claim a
|
|
253
|
+
* guarantee the contract does not make. */
|
|
254
|
+
function evalContextFromRecording(tx) {
|
|
255
|
+
const top = tx.invocations[0];
|
|
256
|
+
if (!top)
|
|
257
|
+
return null;
|
|
258
|
+
return { contract: top.contract, fn: top.fn, args: top.args };
|
|
259
|
+
}
|
|
260
|
+
/** Both `simulate_policy` and `verify_policy` evaluate against a context derived
|
|
261
|
+
* from `permitTx`; neither has anything to evaluate when the recording carries
|
|
262
|
+
* no top-level invocation. */
|
|
263
|
+
function noInvocationError(toolName) {
|
|
264
|
+
return {
|
|
265
|
+
code: TOOL_ERROR_CODE[toolName],
|
|
266
|
+
message: `${toolName}: permitTx carries no invocation to evaluate`,
|
|
267
|
+
severity: 'error',
|
|
268
|
+
retryable: false,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** `simulate_policy` body - evaluate a predicate against one recorded call.
|
|
272
|
+
*
|
|
273
|
+
* The evaluator is a second implementation of the on-chain semantics, and the
|
|
274
|
+
* conformance harness asserts it agrees with the Rust interpreter case for
|
|
275
|
+
* case. A verdict here is therefore a claim about what the contract would do,
|
|
276
|
+
* not a guess. */
|
|
277
|
+
function runSimulatePolicy(raw) {
|
|
278
|
+
const parsed = schemas_ts_1.SimulatePolicyInputSchema.safeParse(raw);
|
|
279
|
+
if (!parsed.success) {
|
|
280
|
+
return { ok: false, error: validationError('simulate_policy', parsed.error.issues) };
|
|
281
|
+
}
|
|
282
|
+
const input = parsed.data;
|
|
283
|
+
const ctx = evalContextFromRecording(input.permitTx);
|
|
284
|
+
if (!ctx)
|
|
285
|
+
return { ok: false, error: noInvocationError('simulate_policy') };
|
|
286
|
+
try {
|
|
287
|
+
const res = (0, index_ts_2.evaluate)(input.predicate, ctx);
|
|
288
|
+
return {
|
|
289
|
+
ok: true,
|
|
290
|
+
data: {
|
|
291
|
+
permitted: res.permit,
|
|
292
|
+
reason: res.permit ? null : res.reason,
|
|
293
|
+
call: { contract: ctx.contract, fn: ctx.fn, argCount: ctx.args.length },
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
catch (e) {
|
|
298
|
+
return toolFailure('simulate_policy', e);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/** `verify_policy` body - the permit case plus a generated deny case per
|
|
302
|
+
* dimension.
|
|
303
|
+
*
|
|
304
|
+
* Two failure modes are being checked, and they are not the same thing. A
|
|
305
|
+
* permit case that denies means the policy is too STRICT: it would refuse the
|
|
306
|
+
* very transaction it was synthesised from. A deny case that permits means it
|
|
307
|
+
* is too LOOSE: some mutation of that transaction still gets through. `ok` is
|
|
308
|
+
* true only when neither holds. */
|
|
309
|
+
function runVerifyPolicy(raw) {
|
|
310
|
+
const parsed = schemas_ts_1.VerifyPolicyInputSchema.safeParse(raw);
|
|
659
311
|
if (!parsed.success) {
|
|
312
|
+
return { ok: false, error: validationError('verify_policy', parsed.error.issues) };
|
|
313
|
+
}
|
|
314
|
+
const input = parsed.data;
|
|
315
|
+
const ctx = evalContextFromRecording(input.permitTx);
|
|
316
|
+
if (!ctx)
|
|
317
|
+
return { ok: false, error: noInvocationError('verify_policy') };
|
|
318
|
+
try {
|
|
319
|
+
const predicate = input.predicate;
|
|
320
|
+
const cases = (0, index_ts_2.generateCases)(predicate, ctx);
|
|
321
|
+
const permitRes = (0, index_ts_2.evaluate)(predicate, cases.permit);
|
|
322
|
+
const denies = cases.denies.map((d) => {
|
|
323
|
+
const r = (0, index_ts_2.evaluate)(predicate, d.ctx);
|
|
324
|
+
return {
|
|
325
|
+
dimension: d.dimension,
|
|
326
|
+
denied: !r.permit,
|
|
327
|
+
reason: r.permit ? null : r.reason,
|
|
328
|
+
};
|
|
329
|
+
});
|
|
660
330
|
return {
|
|
661
|
-
ok:
|
|
662
|
-
|
|
331
|
+
ok: true,
|
|
332
|
+
data: {
|
|
333
|
+
ok: permitRes.permit && denies.every((d) => d.denied),
|
|
334
|
+
permit: { permitted: permitRes.permit, reason: permitRes.permit ? null : permitRes.reason },
|
|
335
|
+
denies,
|
|
336
|
+
dimensionsCovered: denies.length,
|
|
337
|
+
},
|
|
663
338
|
};
|
|
664
339
|
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
return toolFailure('verify_policy', e);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function runGetInterpreterInfo(raw) {
|
|
345
|
+
const parsed = schemas_ts_1.GetInterpreterInfoInputSchema.safeParse(raw);
|
|
346
|
+
if (!parsed.success) {
|
|
347
|
+
return { ok: false, error: validationError('get_interpreter_info', parsed.error.issues) };
|
|
348
|
+
}
|
|
665
349
|
const input = parsed.data;
|
|
666
350
|
const network = input.network ?? 'testnet';
|
|
667
351
|
const expectedRpc = schemas_ts_1.RPC_URL_BY_NETWORK[network];
|
|
@@ -687,17 +371,14 @@ async function runGetInterpreterInfo(raw) {
|
|
|
687
371
|
const info = (0, get_interpreter_info_ts_1.getInterpreterInfo)({
|
|
688
372
|
pinnedAddress,
|
|
689
373
|
pinnedGrammarVersion: schemas_ts_1.PINNED_INTERPRETER_GRAMMAR_VERSION,
|
|
690
|
-
pinnedWasmHash: schemas_ts_1.
|
|
374
|
+
pinnedWasmHash: schemas_ts_1.PINNED_INTERPRETER_WASM_SHA256,
|
|
691
375
|
network,
|
|
692
376
|
...(deployedGrammarVersion !== undefined ? { deployedGrammarVersion } : {}),
|
|
693
377
|
});
|
|
694
378
|
return { ok: true, data: info };
|
|
695
379
|
}
|
|
696
380
|
catch (e) {
|
|
697
|
-
return
|
|
698
|
-
ok: false,
|
|
699
|
-
error: caughtError('get_interpreter_info', 'RECORDING_FAILED', e),
|
|
700
|
-
};
|
|
381
|
+
return toolFailure('get_interpreter_info', e);
|
|
701
382
|
}
|
|
702
383
|
}
|
|
703
384
|
/** Build an InstallRpcClient from an optional URL override + selected
|
|
@@ -751,13 +432,8 @@ function enforceInterpreterPin(policies, allowUnpinned, expectedInterpreterAddre
|
|
|
751
432
|
function enforceRpcPin(toolName, rpcUrl, allowUnpinned, expectedRpc, network) {
|
|
752
433
|
if (!rpcUrl || rpcUrl === expectedRpc || allowUnpinned === true)
|
|
753
434
|
return null;
|
|
754
|
-
const code = toolName === 'install_policy'
|
|
755
|
-
? 'INSTALL_BUILD_FAILED'
|
|
756
|
-
: toolName === 'revoke_policy'
|
|
757
|
-
? 'REVOKE_BUILD_FAILED'
|
|
758
|
-
: 'RECORDING_FAILED';
|
|
759
435
|
return {
|
|
760
|
-
code,
|
|
436
|
+
code: TOOL_ERROR_CODE[toolName],
|
|
761
437
|
message: `${toolName}: rpcUrl must equal the pinned ${expectedRpc} (${network}); set allowUnpinnedRpcUrl: true to opt in to a custom endpoint`,
|
|
762
438
|
severity: 'error',
|
|
763
439
|
retryable: false,
|
|
@@ -784,6 +460,11 @@ function validationError(toolName, issues) {
|
|
|
784
460
|
* error in `details` for the agent to inspect. Exported as a test-only seam
|
|
785
461
|
* so the suite in run/index.test.ts can drive the envelope path without
|
|
786
462
|
* standing up a full recordTransaction pipeline. */
|
|
463
|
+
/** The fail-closed return every tool body uses in its `catch`. Taking only the
|
|
464
|
+
* tool name keeps the tool -> error-code mapping in TOOL_ERROR_CODE alone. */
|
|
465
|
+
function toolFailure(toolName, e) {
|
|
466
|
+
return { ok: false, error: caughtError(toolName, TOOL_ERROR_CODE[toolName], e) };
|
|
467
|
+
}
|
|
787
468
|
function caughtError(toolName, code, e) {
|
|
788
469
|
return {
|
|
789
470
|
code,
|