@crediolabs/policy-synth 0.1.12 → 0.1.14
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/dist/install/build-install-policy.d.ts +218 -0
- package/dist/install/build-install-policy.js +458 -0
- package/dist/install/get-interpreter-info.d.ts +29 -0
- package/dist/install/get-interpreter-info.js +41 -0
- package/dist/install/oz-auth.d.ts +27 -0
- package/dist/install/oz-auth.js +105 -0
- package/dist/run/index.d.ts +49 -4
- package/dist/run/index.js +324 -4
- package/dist/run/schemas.d.ts +1500 -4
- package/dist/run/schemas.js +288 -0
- package/dist-cjs/install/build-install-policy.d.ts +218 -0
- package/dist-cjs/install/build-install-policy.js +462 -0
- package/dist-cjs/install/get-interpreter-info.d.ts +29 -0
- package/dist-cjs/install/get-interpreter-info.js +44 -0
- package/dist-cjs/install/oz-auth.d.ts +27 -0
- package/dist-cjs/install/oz-auth.js +114 -0
- package/dist-cjs/run/index.d.ts +49 -4
- package/dist-cjs/run/index.js +339 -3
- package/dist-cjs/run/schemas.d.ts +1500 -4
- package/dist-cjs/run/schemas.js +289 -1
- package/package.json +1 -1
- package/src/install/build-install-policy.ts +753 -0
- package/src/install/get-interpreter-info.ts +68 -0
- package/src/install/oz-auth.ts +140 -0
- package/src/run/index.ts +405 -5
- package/src/run/schemas.ts +326 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Build the authorization entries OpenZeppelin's smart account requires.
|
|
3
|
+
//
|
|
4
|
+
// This is the piece that makes an end-to-end test of the account -> policy
|
|
5
|
+
// path possible. Without it, every attempt to exercise `__check_auth`
|
|
6
|
+
// produces a false positive: mocked auth skips `__check_auth` entirely,
|
|
7
|
+
// direct invocation is rejected by the host, and recording-mode simulation
|
|
8
|
+
// does not verify auth at all.
|
|
9
|
+
//
|
|
10
|
+
// Two entries are needed per call:
|
|
11
|
+
//
|
|
12
|
+
// 1. The ACCOUNT's entry. Its `signature` slot is not a signature - OZ puts
|
|
13
|
+
// an `AuthPayload { signers, context_rule_ids }` there, which
|
|
14
|
+
// `__check_auth` receives as its `signatures` argument.
|
|
15
|
+
//
|
|
16
|
+
// 2. One entry per DELEGATED signer. `do_check_auth` calls
|
|
17
|
+
// `addr.require_auth_for_args((auth_digest,))` for each, which is a
|
|
18
|
+
// nested authorization requirement the host will not record during
|
|
19
|
+
// simulation (it never runs `__check_auth` in recording mode), so it has
|
|
20
|
+
// to be constructed by hand.
|
|
21
|
+
//
|
|
22
|
+
// The digest OZ binds is NOT the raw auth payload hash:
|
|
23
|
+
//
|
|
24
|
+
// auth_digest = sha256(signature_payload || xdr(context_rule_ids))
|
|
25
|
+
//
|
|
26
|
+
// where `signature_payload` is the standard Soroban authorization preimage
|
|
27
|
+
// hash. See `do_check_auth` in
|
|
28
|
+
// stellar-contracts/packages/accounts/src/smart_account/storage.rs.
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.delegatedSigner = void 0;
|
|
31
|
+
exports.signaturePayload = signaturePayload;
|
|
32
|
+
exports.authDigest = authDigest;
|
|
33
|
+
exports.authPayload = authPayload;
|
|
34
|
+
exports.delegatedSignerEntry = delegatedSignerEntry;
|
|
35
|
+
exports.accountEntry = accountEntry;
|
|
36
|
+
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
37
|
+
const sym = (s) => stellar_sdk_1.xdr.ScVal.scvSymbol(s);
|
|
38
|
+
const u32 = (n) => stellar_sdk_1.xdr.ScVal.scvU32(n);
|
|
39
|
+
const vec = (i) => stellar_sdk_1.xdr.ScVal.scvVec(i);
|
|
40
|
+
const bytes = (b) => stellar_sdk_1.xdr.ScVal.scvBytes(b);
|
|
41
|
+
function struct(fields) {
|
|
42
|
+
return stellar_sdk_1.xdr.ScVal.scvMap(Object.keys(fields)
|
|
43
|
+
.sort()
|
|
44
|
+
.map((k) => new stellar_sdk_1.xdr.ScMapEntry({ key: sym(k), val: fields[k] })));
|
|
45
|
+
}
|
|
46
|
+
/** `Signer::Delegated(addr)`. */
|
|
47
|
+
const delegatedSigner = (a) => vec([sym('Delegated'), new stellar_sdk_1.Address(a).toScVal()]);
|
|
48
|
+
exports.delegatedSigner = delegatedSigner;
|
|
49
|
+
/** The standard Soroban authorization preimage hash for one entry. */
|
|
50
|
+
function signaturePayload(networkPassphrase, nonce, signatureExpirationLedger, invocation) {
|
|
51
|
+
const preimage = stellar_sdk_1.xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(new stellar_sdk_1.xdr.HashIdPreimageSorobanAuthorization({
|
|
52
|
+
networkId: (0, stellar_sdk_1.hash)(Buffer.from(networkPassphrase)),
|
|
53
|
+
nonce,
|
|
54
|
+
signatureExpirationLedger,
|
|
55
|
+
invocation,
|
|
56
|
+
}));
|
|
57
|
+
return (0, stellar_sdk_1.hash)(preimage.toXDR());
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `sha256(signature_payload || xdr(context_rule_ids))`.
|
|
61
|
+
*
|
|
62
|
+
* OZ binds the selected rule ids into the digest so a signature for one rule
|
|
63
|
+
* cannot be replayed against another.
|
|
64
|
+
*/
|
|
65
|
+
function authDigest(payload, contextRuleIds) {
|
|
66
|
+
const idsXdr = vec(contextRuleIds.map(u32)).toXDR();
|
|
67
|
+
return (0, stellar_sdk_1.hash)(Buffer.concat([payload, idsXdr]));
|
|
68
|
+
}
|
|
69
|
+
/** `AuthPayload { signers, context_rule_ids }` - the account's "signature". */
|
|
70
|
+
function authPayload(signerAddresses, contextRuleIds, signatureFor) {
|
|
71
|
+
return struct({
|
|
72
|
+
signers: stellar_sdk_1.xdr.ScVal.scvMap(signerAddresses
|
|
73
|
+
.map((a) => new stellar_sdk_1.xdr.ScMapEntry({ key: (0, exports.delegatedSigner)(a), val: bytes(signatureFor(a)) }))
|
|
74
|
+
// Host maps must be sorted by key.
|
|
75
|
+
.sort((x, y) => Buffer.compare(x.key().toXDR(), y.key().toXDR()))),
|
|
76
|
+
context_rule_ids: vec(contextRuleIds.map(u32)),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The nested entry a `Delegated` signer needs.
|
|
81
|
+
*
|
|
82
|
+
* `require_auth_for_args` authorizes the CURRENT frame, which while
|
|
83
|
+
* `__check_auth` is running is the account contract executing `__check_auth`,
|
|
84
|
+
* with the digest as its single argument.
|
|
85
|
+
*
|
|
86
|
+
* The signer is the transaction source here, so source-account credentials
|
|
87
|
+
* carry it and no separate signature is required.
|
|
88
|
+
*/
|
|
89
|
+
function delegatedSignerEntry(accountId, digest) {
|
|
90
|
+
return new stellar_sdk_1.xdr.SorobanAuthorizationEntry({
|
|
91
|
+
credentials: stellar_sdk_1.xdr.SorobanCredentials.sorobanCredentialsSourceAccount(),
|
|
92
|
+
rootInvocation: new stellar_sdk_1.xdr.SorobanAuthorizedInvocation({
|
|
93
|
+
function: stellar_sdk_1.xdr.SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn(new stellar_sdk_1.xdr.InvokeContractArgs({
|
|
94
|
+
contractAddress: new stellar_sdk_1.Address(accountId).toScAddress(),
|
|
95
|
+
functionName: '__check_auth',
|
|
96
|
+
args: [bytes(digest)],
|
|
97
|
+
})),
|
|
98
|
+
subInvocations: [],
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Rebuild the account's entry with the AuthPayload in the signature slot. */
|
|
103
|
+
function accountEntry(original, signatureExpirationLedger, payload) {
|
|
104
|
+
const creds = original.credentials().address();
|
|
105
|
+
return new stellar_sdk_1.xdr.SorobanAuthorizationEntry({
|
|
106
|
+
credentials: stellar_sdk_1.xdr.SorobanCredentials.sorobanCredentialsAddress(new stellar_sdk_1.xdr.SorobanAddressCredentials({
|
|
107
|
+
address: creds.address(),
|
|
108
|
+
nonce: creds.nonce(),
|
|
109
|
+
signatureExpirationLedger,
|
|
110
|
+
signature: payload,
|
|
111
|
+
})),
|
|
112
|
+
rootInvocation: original.rootInvocation(),
|
|
113
|
+
});
|
|
114
|
+
}
|
package/dist-cjs/run/index.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type ErrorCode, type PredicateNode, type ProposedPolicy, type RecordedTransaction, type ToolError, type ToolResponse } from '../index.ts';
|
|
2
|
+
import { type BuildInstallPolicyResult, type BuildRevokePolicyResult } from '../install/build-install-policy.ts';
|
|
3
|
+
import { getInterpreterInfo } from '../install/get-interpreter-info.ts';
|
|
2
4
|
import type { SimulationResult } from '../verify/envelope.ts';
|
|
3
|
-
import { type RecordTransactionInput, type SynthesizePolicyInput } from './schemas.ts';
|
|
4
|
-
export type { RecordTransactionInput, SynthesizePolicyInput } from './schemas.ts';
|
|
5
|
-
export { ComposeUserResponsesSchema, InterpreterOptionsSchema, MandateSpecSchema, NetworkSchema, OzAdapterConfigSchema, RecordedTransactionSchema, RecordTransactionInputSchema, SynthesizePolicyInputSchema, ToolErrorSchema, } from './schemas.ts';
|
|
5
|
+
import { type RecordTransactionInput, type SimulatePolicyInput, type SynthesizePolicyInput, type VerifyPolicyInput } from './schemas.ts';
|
|
6
|
+
export type { GetInterpreterInfoInput, InstallPolicyInput, RecordTransactionInput, RevokePolicyInput, SimulatePolicyInput, SynthesizePolicyInput, VerifyPolicyInput, } from './schemas.ts';
|
|
7
|
+
export { ComposeUserResponsesSchema, GetInterpreterInfoInputSchema, InstallPolicyInputSchema, InterpreterOptionsSchema, MandateSpecSchema, NetworkSchema, OraclePriceFixtureSchema, OzAdapterConfigSchema, PINNED_INTERPRETER_GRAMMAR_VERSION, PINNED_INTERPRETER_TESTNET_ADDRESS, PINNED_INTERPRETER_WASM_SHA256, PredicateLeafSchema, PredicateNodeSchema, RecordedTransactionSchema, RecordTransactionInputSchema, RevokePolicyInputSchema, SimulatePolicyInputSchema, SynthesizePolicyInputSchema, ToolErrorSchema, VerifyPolicyInputSchema, } from './schemas.ts';
|
|
6
8
|
export type RunRecordTransactionInput = RecordTransactionInput;
|
|
7
9
|
export type RunSynthesizePolicyInput = SynthesizePolicyInput;
|
|
10
|
+
export type RunSimulatePolicyInput = SimulatePolicyInput;
|
|
11
|
+
export type RunVerifyPolicyInput = VerifyPolicyInput;
|
|
8
12
|
/** `record_transaction` body - wraps `recordTransaction`. The tool input
|
|
9
13
|
* matches the core RecordInput minus the injected `fetcher` (the transport
|
|
10
14
|
* layer does not own the RPC). Returns the core ToolResponse unchanged.
|
|
@@ -35,6 +39,47 @@ export declare function runSynthesizePolicy(raw: unknown): Promise<ToolResponse<
|
|
|
35
39
|
simulation: SimulationResult;
|
|
36
40
|
};
|
|
37
41
|
}>;
|
|
42
|
+
/** `simulate_policy` body - thin wrapper over `simulatePolicy`. The engine
|
|
43
|
+
* already returns fail-closed `{ok:false, error}` for runtime failures
|
|
44
|
+
* (SIMULATION_ERROR), so the try/catch envelope is for raw SDK throws
|
|
45
|
+
* only - same pattern as the other two wrappers. The predicate is
|
|
46
|
+
* passed inline (stateless by design; no `proposed_policy_id` lookup). */
|
|
47
|
+
export declare function runSimulatePolicy(raw: unknown): Promise<ToolResponse<SimulationResult>>;
|
|
48
|
+
/** `verify_policy` body - thin wrapper over `verifyPolicy`. The engine
|
|
49
|
+
* already returns `{ok:false, error}` with code VERIFICATION_FAILED when
|
|
50
|
+
* the minimality check fails; the try/catch envelope is for raw SDK
|
|
51
|
+
* throws only. Mirrors `runSimulatePolicy` exactly. */
|
|
52
|
+
export declare function runVerifyPolicy(raw: unknown): Promise<ToolResponse<true>>;
|
|
53
|
+
/** `install_policy` body - thin wrapper over `buildInstallPolicyXdr`.
|
|
54
|
+
* Returns the unsigned Soroban transaction envelope (base64 XDR) the
|
|
55
|
+
* wallet signs. The wallet signature IS the user-confirmation step - no
|
|
56
|
+
* `action_id` two-call pair (the server is stateless, see server.ts:10-12).
|
|
57
|
+
* Per design decision 4, only CALL 1 (account.add_context_rule) is
|
|
58
|
+
* emitted; CALL 2 (interpreter.install) requires the rule id the account
|
|
59
|
+
* assigns in call 1 and is documented under `followUp` in the response.
|
|
60
|
+
*
|
|
61
|
+
* Default-deny: an interpreter policy address other than the pinned
|
|
62
|
+
* testnet interpreter is REFUSED (the smart account would delegate to
|
|
63
|
+
* an interpreter the caller controls); the same applies to a non-pinned
|
|
64
|
+
* RPC URL (the auth nonce the wallet signs comes from the RPC). Both
|
|
65
|
+
* gates accept an explicit opt-in flag. */
|
|
66
|
+
export declare function runInstallPolicy(raw: unknown): Promise<ToolResponse<BuildInstallPolicyResult>>;
|
|
67
|
+
/** `revoke_policy` body - thin wrapper over `buildRevokePolicyXdr`.
|
|
68
|
+
* Emits an unsigned XDR for `account.remove_context_rule(ruleId)`; the
|
|
69
|
+
* smart account itself handles uninstalling each attached policy. Auth
|
|
70
|
+
* is master-only; the source account MUST be the master signer set.
|
|
71
|
+
*
|
|
72
|
+
* Same RPC pin as install: a non-pinned `rpcUrl` is refused unless
|
|
73
|
+
* `allowUnpinnedRpcUrl: true`. Revoke does not carry an interpreter
|
|
74
|
+
* policy payload, so the interpreter pin is not re-checked here. */
|
|
75
|
+
export declare function runRevokePolicy(raw: unknown): Promise<ToolResponse<BuildRevokePolicyResult>>;
|
|
76
|
+
/** `get_interpreter_info` body - thin wrapper over `getInterpreterInfo`.
|
|
77
|
+
* Returns the pinned deployment fingerprint + an optional live
|
|
78
|
+
* `grammar_version()` comparison. The audit field is deliberately
|
|
79
|
+
* OMITTED (phase-04's "audit #44" has no source of truth in the repo -
|
|
80
|
+
* fabricating it would be a lie on a security surface; the live
|
|
81
|
+
* mismatch check is worth MORE). */
|
|
82
|
+
export declare function runGetInterpreterInfo(raw: unknown): Promise<ToolResponse<ReturnType<typeof getInterpreterInfo>>>;
|
|
38
83
|
/** Build a canonical ToolError for a thrown exception caught by the tool
|
|
39
84
|
* envelope. The MCP SDK stringifies thrown objects as "[object Object]" by
|
|
40
85
|
* default, so we extract a string-friendly message and tag the original
|
|
@@ -46,4 +91,4 @@ export declare function runSynthesizePolicy(raw: unknown): Promise<ToolResponse<
|
|
|
46
91
|
* Exported as `_caughtError` (the leading underscore signals the test-only
|
|
47
92
|
* seam) so the suite in run/index.test.ts can drive the envelope path
|
|
48
93
|
* without standing up a full recordTransaction pipeline. */
|
|
49
|
-
export declare function caughtError(toolName: 'record_transaction' | 'synthesize_policy', code: ErrorCode, e: unknown): ToolError;
|
|
94
|
+
export declare function caughtError(toolName: 'record_transaction' | 'synthesize_policy' | 'simulate_policy' | 'verify_policy' | 'install_policy' | 'revoke_policy' | 'get_interpreter_info', code: ErrorCode, e: unknown): ToolError;
|
package/dist-cjs/run/index.js
CHANGED
|
@@ -18,11 +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.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.OzAdapterConfigSchema = exports.NetworkSchema = exports.MandateSpecSchema = exports.InterpreterOptionsSchema = exports.ComposeUserResponsesSchema = void 0;
|
|
21
|
+
exports.VerifyPolicyInputSchema = exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.SimulatePolicyInputSchema = exports.RevokePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.PredicateNodeSchema = exports.PredicateLeafSchema = exports.PINNED_INTERPRETER_WASM_SHA256 = exports.PINNED_INTERPRETER_TESTNET_ADDRESS = exports.PINNED_INTERPRETER_GRAMMAR_VERSION = exports.OzAdapterConfigSchema = exports.OraclePriceFixtureSchema = exports.NetworkSchema = exports.MandateSpecSchema = 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
|
+
exports.runInstallPolicy = runInstallPolicy;
|
|
27
|
+
exports.runRevokePolicy = runRevokePolicy;
|
|
28
|
+
exports.runGetInterpreterInfo = runGetInterpreterInfo;
|
|
24
29
|
exports.caughtError = caughtError;
|
|
30
|
+
const node_crypto_1 = require("node:crypto");
|
|
31
|
+
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
25
32
|
const index_ts_1 = require("../index.js");
|
|
33
|
+
const build_install_policy_ts_1 = require("../install/build-install-policy.js");
|
|
34
|
+
const get_interpreter_info_ts_1 = require("../install/get-interpreter-info.js");
|
|
35
|
+
const index_ts_2 = require("../verify/index.js");
|
|
26
36
|
const schemas_ts_1 = require("./schemas.js");
|
|
27
37
|
// Re-export the underlying Zod schemas so the MCP package (and any other
|
|
28
38
|
// downstream consumer) can import the canonical input shapes from the same
|
|
@@ -30,14 +40,25 @@ const schemas_ts_1 = require("./schemas.js");
|
|
|
30
40
|
// truth - MCP tool shapes are derived from them.
|
|
31
41
|
var schemas_ts_2 = require("./schemas.js");
|
|
32
42
|
Object.defineProperty(exports, "ComposeUserResponsesSchema", { enumerable: true, get: function () { return schemas_ts_2.ComposeUserResponsesSchema; } });
|
|
43
|
+
Object.defineProperty(exports, "GetInterpreterInfoInputSchema", { enumerable: true, get: function () { return schemas_ts_2.GetInterpreterInfoInputSchema; } });
|
|
44
|
+
Object.defineProperty(exports, "InstallPolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.InstallPolicyInputSchema; } });
|
|
33
45
|
Object.defineProperty(exports, "InterpreterOptionsSchema", { enumerable: true, get: function () { return schemas_ts_2.InterpreterOptionsSchema; } });
|
|
34
46
|
Object.defineProperty(exports, "MandateSpecSchema", { enumerable: true, get: function () { return schemas_ts_2.MandateSpecSchema; } });
|
|
35
47
|
Object.defineProperty(exports, "NetworkSchema", { enumerable: true, get: function () { return schemas_ts_2.NetworkSchema; } });
|
|
48
|
+
Object.defineProperty(exports, "OraclePriceFixtureSchema", { enumerable: true, get: function () { return schemas_ts_2.OraclePriceFixtureSchema; } });
|
|
36
49
|
Object.defineProperty(exports, "OzAdapterConfigSchema", { enumerable: true, get: function () { return schemas_ts_2.OzAdapterConfigSchema; } });
|
|
50
|
+
Object.defineProperty(exports, "PINNED_INTERPRETER_GRAMMAR_VERSION", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_GRAMMAR_VERSION; } });
|
|
51
|
+
Object.defineProperty(exports, "PINNED_INTERPRETER_TESTNET_ADDRESS", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_TESTNET_ADDRESS; } });
|
|
52
|
+
Object.defineProperty(exports, "PINNED_INTERPRETER_WASM_SHA256", { enumerable: true, get: function () { return schemas_ts_2.PINNED_INTERPRETER_WASM_SHA256; } });
|
|
53
|
+
Object.defineProperty(exports, "PredicateLeafSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateLeafSchema; } });
|
|
54
|
+
Object.defineProperty(exports, "PredicateNodeSchema", { enumerable: true, get: function () { return schemas_ts_2.PredicateNodeSchema; } });
|
|
37
55
|
Object.defineProperty(exports, "RecordedTransactionSchema", { enumerable: true, get: function () { return schemas_ts_2.RecordedTransactionSchema; } });
|
|
38
56
|
Object.defineProperty(exports, "RecordTransactionInputSchema", { enumerable: true, get: function () { return schemas_ts_2.RecordTransactionInputSchema; } });
|
|
57
|
+
Object.defineProperty(exports, "RevokePolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.RevokePolicyInputSchema; } });
|
|
58
|
+
Object.defineProperty(exports, "SimulatePolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.SimulatePolicyInputSchema; } });
|
|
39
59
|
Object.defineProperty(exports, "SynthesizePolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.SynthesizePolicyInputSchema; } });
|
|
40
60
|
Object.defineProperty(exports, "ToolErrorSchema", { enumerable: true, get: function () { return schemas_ts_2.ToolErrorSchema; } });
|
|
61
|
+
Object.defineProperty(exports, "VerifyPolicyInputSchema", { enumerable: true, get: function () { return schemas_ts_2.VerifyPolicyInputSchema; } });
|
|
41
62
|
/** `record_transaction` body - wraps `recordTransaction`. The tool input
|
|
42
63
|
* matches the core RecordInput minus the injected `fetcher` (the transport
|
|
43
64
|
* layer does not own the RPC). Returns the core ToolResponse unchanged.
|
|
@@ -132,11 +153,323 @@ function resolveOzConfig(input) {
|
|
|
132
153
|
// placeholder OZ instance addresses are deterministic.
|
|
133
154
|
return (0, index_ts_1.placeholderOzConfig)('mainnet');
|
|
134
155
|
}
|
|
156
|
+
/** `simulate_policy` body - thin wrapper over `simulatePolicy`. The engine
|
|
157
|
+
* already returns fail-closed `{ok:false, error}` for runtime failures
|
|
158
|
+
* (SIMULATION_ERROR), so the try/catch envelope is for raw SDK throws
|
|
159
|
+
* only - same pattern as the other two wrappers. The predicate is
|
|
160
|
+
* passed inline (stateless by design; no `proposed_policy_id` lookup). */
|
|
161
|
+
async function runSimulatePolicy(raw) {
|
|
162
|
+
const parsed = schemas_ts_1.SimulatePolicyInputSchema.safeParse(raw);
|
|
163
|
+
if (!parsed.success) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
error: validationError('simulate_policy', parsed.error.issues),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const input = parsed.data;
|
|
170
|
+
try {
|
|
171
|
+
// The recursive PredicateNodeSchema + ContractInvocationSchema are
|
|
172
|
+
// typed `z.ZodType<unknown>` to survive TS's circular inference; the
|
|
173
|
+
// engine wants typed `PredicateNode | null` + `RecordedTransaction`.
|
|
174
|
+
// The schema already validated the shape, so assert through the
|
|
175
|
+
// unknown back to the core types. Same pattern as the recordedTx
|
|
176
|
+
// cast in `runSynthesizePolicy`.
|
|
177
|
+
return (0, index_ts_2.simulatePolicy)(input.predicate, input.permitTx, {
|
|
178
|
+
...(input.validUntilLedger !== undefined
|
|
179
|
+
? { validUntilLedger: input.validUntilLedger }
|
|
180
|
+
: {}),
|
|
181
|
+
...(input.oraclePricesByAsset !== undefined
|
|
182
|
+
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
183
|
+
: {}),
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
error: caughtError('simulate_policy', 'SIMULATION_ERROR', e),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/** `verify_policy` body - thin wrapper over `verifyPolicy`. The engine
|
|
194
|
+
* already returns `{ok:false, error}` with code VERIFICATION_FAILED when
|
|
195
|
+
* the minimality check fails; the try/catch envelope is for raw SDK
|
|
196
|
+
* throws only. Mirrors `runSimulatePolicy` exactly. */
|
|
197
|
+
async function runVerifyPolicy(raw) {
|
|
198
|
+
const parsed = schemas_ts_1.VerifyPolicyInputSchema.safeParse(raw);
|
|
199
|
+
if (!parsed.success) {
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
error: validationError('verify_policy', parsed.error.issues),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const input = parsed.data;
|
|
206
|
+
try {
|
|
207
|
+
// Same cast as runSimulatePolicy: the recursive schemas are typed
|
|
208
|
+
// `unknown`; the engine wants typed `PredicateNode` +
|
|
209
|
+
// `RecordedTransaction`. The schema already validated the shape.
|
|
210
|
+
return (0, index_ts_2.verifyPolicy)(input.predicate, input.permitTx, {
|
|
211
|
+
...(input.validUntilLedger !== undefined ? { validUntilLedger: input.validUntilLedger } : {}),
|
|
212
|
+
...(input.oraclePricesByAsset !== undefined
|
|
213
|
+
? { oraclePricesByAsset: input.oraclePricesByAsset }
|
|
214
|
+
: {}),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
error: caughtError('verify_policy', 'VERIFICATION_FAILED', e),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** `install_policy` body - thin wrapper over `buildInstallPolicyXdr`.
|
|
225
|
+
* Returns the unsigned Soroban transaction envelope (base64 XDR) the
|
|
226
|
+
* wallet signs. The wallet signature IS the user-confirmation step - no
|
|
227
|
+
* `action_id` two-call pair (the server is stateless, see server.ts:10-12).
|
|
228
|
+
* Per design decision 4, only CALL 1 (account.add_context_rule) is
|
|
229
|
+
* emitted; CALL 2 (interpreter.install) requires the rule id the account
|
|
230
|
+
* assigns in call 1 and is documented under `followUp` in the response.
|
|
231
|
+
*
|
|
232
|
+
* Default-deny: an interpreter policy address other than the pinned
|
|
233
|
+
* testnet interpreter is REFUSED (the smart account would delegate to
|
|
234
|
+
* an interpreter the caller controls); the same applies to a non-pinned
|
|
235
|
+
* RPC URL (the auth nonce the wallet signs comes from the RPC). Both
|
|
236
|
+
* gates accept an explicit opt-in flag. */
|
|
237
|
+
async function runInstallPolicy(raw) {
|
|
238
|
+
const parsed = schemas_ts_1.InstallPolicyInputSchema.safeParse(raw);
|
|
239
|
+
if (!parsed.success) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
error: validationError('install_policy', parsed.error.issues),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const input = parsed.data;
|
|
246
|
+
// ---- Pinning gates (default-deny) ----
|
|
247
|
+
const pinningError = enforceInterpreterPin(input.rule.policies, input.allowUnpinnedInterpreter);
|
|
248
|
+
if (pinningError) {
|
|
249
|
+
return { ok: false, error: pinningError };
|
|
250
|
+
}
|
|
251
|
+
if (input.rpcUrl && input.rpcUrl !== schemas_ts_1.TESTNET_RPC_URL && input.allowUnpinnedRpcUrl !== true) {
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
error: {
|
|
255
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
256
|
+
message: `install_policy: rpcUrl must equal the pinned TESTNET_RPC_URL; set allowUnpinnedRpcUrl: true to opt in to a custom endpoint`,
|
|
257
|
+
severity: 'error',
|
|
258
|
+
retryable: false,
|
|
259
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
let rpcClient;
|
|
264
|
+
try {
|
|
265
|
+
rpcClient = buildRpcClientFromInput(input.rpcUrl);
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const interpreterPolicy = input.rule.policies.find((p) => p.kind === 'interpreter');
|
|
275
|
+
const encodedPredicate = interpreterPolicy?.predicateBlobBase64 ?? '';
|
|
276
|
+
const predicateHash = (0, node_crypto_1.createHash)('sha256')
|
|
277
|
+
.update(Buffer.from(encodedPredicate, 'base64'))
|
|
278
|
+
.digest('hex');
|
|
279
|
+
const result = await (0, build_install_policy_ts_1.buildInstallPolicyXdr)({
|
|
280
|
+
smartAccount: input.smartAccount,
|
|
281
|
+
sourceAccount: input.sourceAccount,
|
|
282
|
+
networkPassphrase: schemas_ts_1.NETWORK_PASSPHRASES.testnet,
|
|
283
|
+
rule: input.rule,
|
|
284
|
+
installNonce: input.installNonce,
|
|
285
|
+
encodedPredicate,
|
|
286
|
+
predicateHash,
|
|
287
|
+
rpc: rpcClient,
|
|
288
|
+
...(input.baseFee !== undefined ? { baseFee: input.baseFee } : {}),
|
|
289
|
+
});
|
|
290
|
+
return { ok: true, data: result };
|
|
291
|
+
}
|
|
292
|
+
catch (e) {
|
|
293
|
+
return {
|
|
294
|
+
ok: false,
|
|
295
|
+
error: caughtError('install_policy', 'INSTALL_BUILD_FAILED', e),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/** `revoke_policy` body - thin wrapper over `buildRevokePolicyXdr`.
|
|
300
|
+
* Emits an unsigned XDR for `account.remove_context_rule(ruleId)`; the
|
|
301
|
+
* smart account itself handles uninstalling each attached policy. Auth
|
|
302
|
+
* is master-only; the source account MUST be the master signer set.
|
|
303
|
+
*
|
|
304
|
+
* Same RPC pin as install: a non-pinned `rpcUrl` is refused unless
|
|
305
|
+
* `allowUnpinnedRpcUrl: true`. Revoke does not carry an interpreter
|
|
306
|
+
* policy payload, so the interpreter pin is not re-checked here. */
|
|
307
|
+
async function runRevokePolicy(raw) {
|
|
308
|
+
const parsed = schemas_ts_1.RevokePolicyInputSchema.safeParse(raw);
|
|
309
|
+
if (!parsed.success) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
error: validationError('revoke_policy', parsed.error.issues),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
const input = parsed.data;
|
|
316
|
+
if (input.rpcUrl && input.rpcUrl !== schemas_ts_1.TESTNET_RPC_URL && input.allowUnpinnedRpcUrl !== true) {
|
|
317
|
+
return {
|
|
318
|
+
ok: false,
|
|
319
|
+
error: {
|
|
320
|
+
code: 'REVOKE_BUILD_FAILED',
|
|
321
|
+
message: `revoke_policy: rpcUrl must equal the pinned TESTNET_RPC_URL; set allowUnpinnedRpcUrl: true to opt in to a custom endpoint`,
|
|
322
|
+
severity: 'error',
|
|
323
|
+
retryable: false,
|
|
324
|
+
remediation: { toolCall: { name: 'revoke_policy', args: {} } },
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
let rpcClient;
|
|
329
|
+
try {
|
|
330
|
+
rpcClient = buildRpcClientFromInput(input.rpcUrl);
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const result = await (0, build_install_policy_ts_1.buildRevokePolicyXdr)({
|
|
340
|
+
smartAccount: input.smartAccount,
|
|
341
|
+
sourceAccount: input.sourceAccount,
|
|
342
|
+
ruleId: input.ruleId,
|
|
343
|
+
networkPassphrase: schemas_ts_1.NETWORK_PASSPHRASES.testnet,
|
|
344
|
+
rpc: rpcClient,
|
|
345
|
+
...(input.baseFee !== undefined ? { baseFee: input.baseFee } : {}),
|
|
346
|
+
});
|
|
347
|
+
return { ok: true, data: result };
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
return {
|
|
351
|
+
ok: false,
|
|
352
|
+
error: caughtError('revoke_policy', 'REVOKE_BUILD_FAILED', e),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/** `get_interpreter_info` body - thin wrapper over `getInterpreterInfo`.
|
|
357
|
+
* Returns the pinned deployment fingerprint + an optional live
|
|
358
|
+
* `grammar_version()` comparison. The audit field is deliberately
|
|
359
|
+
* OMITTED (phase-04's "audit #44" has no source of truth in the repo -
|
|
360
|
+
* fabricating it would be a lie on a security surface; the live
|
|
361
|
+
* mismatch check is worth MORE). */
|
|
362
|
+
async function runGetInterpreterInfo(raw) {
|
|
363
|
+
const parsed = schemas_ts_1.GetInterpreterInfoInputSchema.safeParse(raw);
|
|
364
|
+
if (!parsed.success) {
|
|
365
|
+
return {
|
|
366
|
+
ok: false,
|
|
367
|
+
error: validationError('get_interpreter_info', parsed.error.issues),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
const input = parsed.data;
|
|
371
|
+
const network = input.network ?? 'testnet';
|
|
372
|
+
// Asking about mainnet used to return the TESTNET pin with `network:
|
|
373
|
+
// 'mainnet'` stamped on it. DEPLOYMENTS.md:3 says plainly "Testnet only.
|
|
374
|
+
// Nothing is deployed to mainnet yet", so that answer described a contract
|
|
375
|
+
// that does not exist at an address that is not on that network. This tool
|
|
376
|
+
// exists to tell a caller what they are installing against; inventing a
|
|
377
|
+
// mainnet deployment is the same failure as inventing an audit reference.
|
|
378
|
+
if (network === 'mainnet') {
|
|
379
|
+
return {
|
|
380
|
+
ok: false,
|
|
381
|
+
error: {
|
|
382
|
+
code: 'RECORDING_FAILED',
|
|
383
|
+
message: 'get_interpreter_info: no interpreter is deployed to mainnet. The pinned address and wasm hash are testnet-only; mainnet deployment is gated on the interpreter audit.',
|
|
384
|
+
severity: 'error',
|
|
385
|
+
retryable: false,
|
|
386
|
+
remediation: {
|
|
387
|
+
toolCall: { name: 'get_interpreter_info', args: { network: 'testnet' } },
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
let deployedGrammarVersion;
|
|
394
|
+
if (input.verifyLive === true) {
|
|
395
|
+
const rpcClient = buildRpcClientFromInput(input.rpcUrl);
|
|
396
|
+
deployedGrammarVersion = await rpcClient.getContractVersion(schemas_ts_1.PINNED_INTERPRETER_TESTNET_ADDRESS);
|
|
397
|
+
}
|
|
398
|
+
const info = (0, get_interpreter_info_ts_1.getInterpreterInfo)({
|
|
399
|
+
pinnedAddress: schemas_ts_1.PINNED_INTERPRETER_TESTNET_ADDRESS,
|
|
400
|
+
pinnedGrammarVersion: schemas_ts_1.PINNED_INTERPRETER_GRAMMAR_VERSION,
|
|
401
|
+
pinnedWasmHash: schemas_ts_1.PINNED_INTERPRETER_WASM_SHA256,
|
|
402
|
+
network,
|
|
403
|
+
...(deployedGrammarVersion !== undefined ? { deployedGrammarVersion } : {}),
|
|
404
|
+
});
|
|
405
|
+
return { ok: true, data: info };
|
|
406
|
+
}
|
|
407
|
+
catch (e) {
|
|
408
|
+
return {
|
|
409
|
+
ok: false,
|
|
410
|
+
error: caughtError('get_interpreter_info', 'RECORDING_FAILED', e),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/** Build an InstallRpcClient from an optional URL override, falling back
|
|
415
|
+
* to the public testnet RPC (matching the design-decision rule that we
|
|
416
|
+
* reuse `record/rpc.ts` and never hard-code a different RPC client). */
|
|
417
|
+
function buildRpcClientFromInput(urlOverride) {
|
|
418
|
+
if (urlOverride) {
|
|
419
|
+
return (0, build_install_policy_ts_1.rpcClientFromServer)(new stellar_sdk_1.rpc.Server(urlOverride, { allowHttp: false }), schemas_ts_1.NETWORK_PASSPHRASES.testnet);
|
|
420
|
+
}
|
|
421
|
+
// Default: public testnet RPC. Matches the brief; the mainnet pin would
|
|
422
|
+
// need a separate deploy and is out of scope for this tool.
|
|
423
|
+
//
|
|
424
|
+
// NOT `createRpcServer` - that returns an RpcFetcher, a bare
|
|
425
|
+
// `(hash) => Promise<SorobanTxResponse|null>` for the RECORDER. Passing it
|
|
426
|
+
// here produced a client whose `getAccount` was undefined, so every live
|
|
427
|
+
// call died with "server.getAccount is not a function". The install path
|
|
428
|
+
// needs the full Server surface.
|
|
429
|
+
return (0, build_install_policy_ts_1.rpcClientFromServer)(new stellar_sdk_1.rpc.Server(schemas_ts_1.TESTNET_RPC_URL, { allowHttp: false }), schemas_ts_1.NETWORK_PASSPHRASES.testnet);
|
|
430
|
+
}
|
|
431
|
+
/** Default-deny: refuse any interpreter policy whose address differs from
|
|
432
|
+
* the pinned testnet interpreter. An interpreter the caller controls
|
|
433
|
+
* can permit anything, so the smart account's authorization must bind
|
|
434
|
+
* to the pinned contract unless the caller explicitly opts in via
|
|
435
|
+
* `allowUnpinnedInterpreter`. OZ built-in policies are not interpreters
|
|
436
|
+
* and pass through unchanged. Returns a ToolError to surface through
|
|
437
|
+
* the run-layer envelope, or null when the policies are all pinned. */
|
|
438
|
+
function enforceInterpreterPin(policies, allowUnpinned) {
|
|
439
|
+
for (const p of policies) {
|
|
440
|
+
if (p.kind !== 'interpreter')
|
|
441
|
+
continue;
|
|
442
|
+
if (p.interpreterAddress === schemas_ts_1.PINNED_INTERPRETER_TESTNET_ADDRESS)
|
|
443
|
+
continue;
|
|
444
|
+
if (allowUnpinned === true)
|
|
445
|
+
continue;
|
|
446
|
+
return {
|
|
447
|
+
code: 'INSTALL_BUILD_FAILED',
|
|
448
|
+
message: `install_policy: interpreter policy address ${p.interpreterAddress} != pinned ${schemas_ts_1.PINNED_INTERPRETER_TESTNET_ADDRESS}; set allowUnpinnedInterpreter: true to opt in to a non-pinned interpreter`,
|
|
449
|
+
severity: 'error',
|
|
450
|
+
retryable: false,
|
|
451
|
+
remediation: { toolCall: { name: 'install_policy', args: {} } },
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
135
456
|
/** Build a canonical ToolError for a Zod validation failure. The remediation
|
|
136
457
|
* hint points the agent back at the right tool with an empty arg bag - the
|
|
137
458
|
* tool name IS the machine-readable hint. */
|
|
138
459
|
function validationError(toolName, issues) {
|
|
139
|
-
const code = toolName === 'record_transaction'
|
|
460
|
+
const code = toolName === 'record_transaction'
|
|
461
|
+
? 'RECORDING_FAILED'
|
|
462
|
+
: toolName === 'synthesize_policy'
|
|
463
|
+
? 'SYNTHESIS_ERROR'
|
|
464
|
+
: toolName === 'simulate_policy'
|
|
465
|
+
? 'SIMULATION_ERROR'
|
|
466
|
+
: toolName === 'verify_policy'
|
|
467
|
+
? 'VERIFICATION_FAILED'
|
|
468
|
+
: toolName === 'install_policy'
|
|
469
|
+
? 'INSTALL_BUILD_FAILED'
|
|
470
|
+
: toolName === 'revoke_policy'
|
|
471
|
+
? 'REVOKE_BUILD_FAILED'
|
|
472
|
+
: 'RECORDING_FAILED';
|
|
140
473
|
return {
|
|
141
474
|
code,
|
|
142
475
|
message: `${toolName}: invalid input: ${issues
|
|
@@ -217,8 +550,11 @@ function safeStringify(v) {
|
|
|
217
550
|
return value.toString();
|
|
218
551
|
if (typeof value === 'function')
|
|
219
552
|
return `[function ${value.name || 'anonymous'}]`;
|
|
553
|
+
// `stack` is a server-controlled diagnostic. Stack traces from a host
|
|
554
|
+
// we do not own are reconnaissance, not a signal the caller can act on.
|
|
555
|
+
// Surface `name` + `message` only.
|
|
220
556
|
if (value instanceof Error) {
|
|
221
|
-
return { name: value.name, message: value.message
|
|
557
|
+
return { name: value.name, message: value.message };
|
|
222
558
|
}
|
|
223
559
|
if (value !== null && typeof value === 'object') {
|
|
224
560
|
if (seen.has(value))
|