@crediolabs/policy-synth 0.1.5 → 0.1.6
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/record/decode.js +43 -0
- package/dist/record/freshness.d.ts +14 -1
- package/dist/record/freshness.js +32 -2
- package/dist/record/index.d.ts +11 -0
- package/dist/record/index.js +25 -0
- package/dist/record/movements.d.ts +15 -3
- package/dist/record/movements.js +42 -7
- package/dist/run/index.d.ts +13 -1
- package/dist/run/index.js +76 -18
- package/dist/run/schemas.d.ts +13 -0
- package/dist/run/schemas.js +8 -0
- package/dist/synth/address.d.ts +7 -0
- package/dist/synth/address.js +12 -0
- package/dist/synth/compose-from-recording.d.ts +4 -3
- package/dist/synth/compose-from-recording.js +16 -6
- package/dist/synth/deny-cases.d.ts +12 -2
- package/dist/synth/deny-cases.js +58 -2
- package/dist/synth/index.d.ts +1 -0
- package/dist/synth/index.js +4 -0
- package/dist/synth/minimize.d.ts +1 -1
- package/dist/synth/minimize.js +3 -3
- package/dist/synth/synthesize-from-recording.js +54 -6
- package/dist/types.d.ts +24 -1
- package/dist-cjs/record/decode.js +43 -0
- package/dist-cjs/record/freshness.d.ts +14 -1
- package/dist-cjs/record/freshness.js +32 -2
- package/dist-cjs/record/index.d.ts +11 -0
- package/dist-cjs/record/index.js +25 -0
- package/dist-cjs/record/movements.d.ts +15 -3
- package/dist-cjs/record/movements.js +42 -7
- package/dist-cjs/run/index.d.ts +13 -1
- package/dist-cjs/run/index.js +76 -17
- package/dist-cjs/run/schemas.d.ts +13 -0
- package/dist-cjs/run/schemas.js +8 -0
- package/dist-cjs/synth/address.d.ts +7 -0
- package/dist-cjs/synth/address.js +15 -0
- package/dist-cjs/synth/compose-from-recording.d.ts +4 -3
- package/dist-cjs/synth/compose-from-recording.js +16 -6
- package/dist-cjs/synth/deny-cases.d.ts +12 -2
- package/dist-cjs/synth/deny-cases.js +60 -2
- package/dist-cjs/synth/index.d.ts +1 -0
- package/dist-cjs/synth/index.js +6 -1
- package/dist-cjs/synth/minimize.d.ts +1 -1
- package/dist-cjs/synth/minimize.js +3 -3
- package/dist-cjs/synth/synthesize-from-recording.js +53 -5
- package/dist-cjs/types.d.ts +24 -1
- package/package.json +1 -1
- package/src/contracts/policy-template/OZ_POLICY_TRAIT.md +171 -0
- package/src/record/corpus-fixtures.json +532 -0
- package/src/record/decode.ts +42 -0
- package/src/record/freshness.ts +34 -2
- package/src/record/index.ts +40 -0
- package/src/record/movements.ts +40 -7
- package/src/run/index.ts +80 -16
- package/src/run/schemas.ts +8 -0
- package/src/synth/address.ts +14 -0
- package/src/synth/compose-from-recording.ts +20 -9
- package/src/synth/deny-cases.ts +66 -2
- package/src/synth/index.ts +4 -0
- package/src/synth/minimize.ts +7 -3
- package/src/synth/synthesize-from-recording.ts +59 -6
- package/src/types.ts +18 -1
package/dist-cjs/run/index.js
CHANGED
|
@@ -21,6 +21,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
21
21
|
exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.RecordTransactionInputSchema = exports.RecordedTransactionSchema = exports.OzAdapterConfigSchema = exports.NetworkSchema = exports.MandateSpecSchema = exports.InterpreterOptionsSchema = exports.ComposeUserResponsesSchema = void 0;
|
|
22
22
|
exports.runRecordTransaction = runRecordTransaction;
|
|
23
23
|
exports.runSynthesizePolicy = runSynthesizePolicy;
|
|
24
|
+
exports.caughtError = caughtError;
|
|
24
25
|
const index_ts_1 = require("../index.js");
|
|
25
26
|
const schemas_ts_1 = require("./schemas.js");
|
|
26
27
|
// Re-export the underlying Zod schemas so the MCP package (and any other
|
|
@@ -148,23 +149,19 @@ function validationError(toolName, issues) {
|
|
|
148
149
|
};
|
|
149
150
|
}
|
|
150
151
|
/** Build a canonical ToolError for a thrown exception caught by the tool
|
|
151
|
-
* envelope.
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* for `
|
|
156
|
-
*
|
|
152
|
+
* envelope. The MCP SDK stringifies thrown objects as "[object Object]" by
|
|
153
|
+
* default, so we extract a string-friendly message and tag the original
|
|
154
|
+
* error in `details` for the agent to inspect. The `code` is the tool's
|
|
155
|
+
* domain code (RECORDING_FAILED for `record_transaction`, SYNTHESIS_ERROR
|
|
156
|
+
* for `synthesize_policy`) so the agent dispatches on the same code the
|
|
157
|
+
* structured ToolError would carry.
|
|
158
|
+
*
|
|
159
|
+
* Exported as `_caughtError` (the leading underscore signals the test-only
|
|
160
|
+
* seam) so the suite in run/index.test.ts can drive the envelope path
|
|
161
|
+
* without standing up a full recordTransaction pipeline. */
|
|
157
162
|
function caughtError(toolName, code, e) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
message = e.message || `${toolName}: unknown error`;
|
|
161
|
-
}
|
|
162
|
-
else if (typeof e === 'string') {
|
|
163
|
-
message = e;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
message = `${toolName}: caught non-Error throw of type ${typeof e}`;
|
|
167
|
-
}
|
|
163
|
+
const message = describeThrown(e, toolName);
|
|
164
|
+
const details = { thrown: safeStringify(e) };
|
|
168
165
|
return {
|
|
169
166
|
code,
|
|
170
167
|
message: `${toolName}: unhandled throw escaped core envelope: ${message}`,
|
|
@@ -173,6 +170,68 @@ function caughtError(toolName, code, e) {
|
|
|
173
170
|
remediation: {
|
|
174
171
|
toolCall: { name: toolName, args: {} },
|
|
175
172
|
},
|
|
176
|
-
details
|
|
173
|
+
details,
|
|
177
174
|
};
|
|
178
175
|
}
|
|
176
|
+
/** Build a human-readable message for an unknown caught value. Order matters:
|
|
177
|
+
* 1. `Error` instances use `.message` (or fallback to the class name).
|
|
178
|
+
* 2. Native strings pass through verbatim.
|
|
179
|
+
* 3. Objects with a string `message` field use that field (mirrors the
|
|
180
|
+
* shape thrown by some SDKs that package errors as plain objects).
|
|
181
|
+
* 4. Anything else falls back to a JSON-shaped summary, never to
|
|
182
|
+
* "[object Object]".
|
|
183
|
+
* The full payload is also captured in `details.thrown` via
|
|
184
|
+
* `safeStringify` so the agent can inspect the original value without
|
|
185
|
+
* risking an infinite loop on circular refs. */
|
|
186
|
+
function describeThrown(e, toolName) {
|
|
187
|
+
if (e instanceof Error) {
|
|
188
|
+
return e.message || `${toolName}: ${e.name || 'Error'}`;
|
|
189
|
+
}
|
|
190
|
+
if (typeof e === 'string') {
|
|
191
|
+
return e;
|
|
192
|
+
}
|
|
193
|
+
if (e !== null && typeof e === 'object') {
|
|
194
|
+
const obj = e;
|
|
195
|
+
const m = obj.message;
|
|
196
|
+
if (typeof m === 'string' && m.length > 0) {
|
|
197
|
+
return truncate(m);
|
|
198
|
+
}
|
|
199
|
+
return truncate(safeStringify(e));
|
|
200
|
+
}
|
|
201
|
+
return `${toolName}: caught non-Error throw of type ${typeof e}`;
|
|
202
|
+
}
|
|
203
|
+
/** Hard cap on the human-readable message embedded in the ToolError. The full
|
|
204
|
+
* payload is still preserved in `details.thrown` so the agent can inspect
|
|
205
|
+
* it - the truncation is only to keep the top-level message small enough to
|
|
206
|
+
* fit comfortably in transport logs. */
|
|
207
|
+
const MAX_MESSAGE_LEN = 512;
|
|
208
|
+
/** JSON.stringify that survives circular refs and very large payloads. The
|
|
209
|
+
* output is itself bounded by the same `MAX_DETAILS_LEN` so a thrown object
|
|
210
|
+
* with megabytes of buffer data cannot bloat the WHOLE envelope. */
|
|
211
|
+
const MAX_DETAILS_LEN = 4096;
|
|
212
|
+
function safeStringify(v) {
|
|
213
|
+
const seen = new WeakSet();
|
|
214
|
+
const json = JSON.stringify(v, (_k, value) => {
|
|
215
|
+
if (typeof value === 'bigint')
|
|
216
|
+
return value.toString();
|
|
217
|
+
if (typeof value === 'function')
|
|
218
|
+
return `[function ${value.name || 'anonymous'}]`;
|
|
219
|
+
if (value instanceof Error) {
|
|
220
|
+
return { name: value.name, message: value.message, stack: value.stack };
|
|
221
|
+
}
|
|
222
|
+
if (value !== null && typeof value === 'object') {
|
|
223
|
+
if (seen.has(value))
|
|
224
|
+
return '[Circular]';
|
|
225
|
+
seen.add(value);
|
|
226
|
+
}
|
|
227
|
+
return value;
|
|
228
|
+
}, 2);
|
|
229
|
+
if (json === undefined)
|
|
230
|
+
return '<unserializable>';
|
|
231
|
+
return truncate(json, MAX_DETAILS_LEN);
|
|
232
|
+
}
|
|
233
|
+
function truncate(s, cap = MAX_MESSAGE_LEN) {
|
|
234
|
+
if (s.length <= cap)
|
|
235
|
+
return s;
|
|
236
|
+
return `${s.slice(0, cap)}\n…[truncated ${s.length - cap} chars]`;
|
|
237
|
+
}
|
|
@@ -446,16 +446,19 @@ export declare const ComposeUserResponsesSchema: z.ZodObject<{
|
|
|
446
446
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
447
447
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
448
448
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
449
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
449
450
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
450
451
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
451
452
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
452
453
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
453
454
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
455
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
454
456
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
455
457
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
456
458
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
457
459
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
458
460
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
461
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
459
462
|
}, z.ZodTypeAny, "passthrough">>;
|
|
460
463
|
/** OzAdapterConfig - the per-network OZ built-in instance addresses. */
|
|
461
464
|
export declare const OzAdapterConfigSchema: z.ZodObject<{
|
|
@@ -1003,16 +1006,19 @@ export declare const SynthesizePolicyRecordingInputSchema: z.ZodObject<{
|
|
|
1003
1006
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1004
1007
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1005
1008
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1009
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1006
1010
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
1007
1011
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
1008
1012
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1009
1013
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1010
1014
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1015
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1011
1016
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
1012
1017
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
1013
1018
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1014
1019
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1015
1020
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1021
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1016
1022
|
}, z.ZodTypeAny, "passthrough">>>;
|
|
1017
1023
|
confidenceOverride: z.ZodOptional<z.ZodObject<{
|
|
1018
1024
|
threshold: z.ZodNumber;
|
|
@@ -1130,6 +1136,7 @@ export declare const SynthesizePolicyRecordingInputSchema: z.ZodObject<{
|
|
|
1130
1136
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1131
1137
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1132
1138
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1139
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1133
1140
|
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
1134
1141
|
confidenceOverride?: {
|
|
1135
1142
|
threshold: number;
|
|
@@ -1193,6 +1200,7 @@ export declare const SynthesizePolicyRecordingInputSchema: z.ZodObject<{
|
|
|
1193
1200
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1194
1201
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1195
1202
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1203
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1196
1204
|
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
1197
1205
|
confidenceOverride?: {
|
|
1198
1206
|
threshold: number;
|
|
@@ -1649,16 +1657,19 @@ export declare const SynthesizePolicyInputSchema: z.ZodDiscriminatedUnion<"sourc
|
|
|
1649
1657
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1650
1658
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1651
1659
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1660
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1652
1661
|
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
1653
1662
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
1654
1663
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1655
1664
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1656
1665
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1666
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1657
1667
|
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
1658
1668
|
windowSeconds: z.ZodOptional<z.ZodNumber>;
|
|
1659
1669
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1660
1670
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1661
1671
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1672
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1662
1673
|
}, z.ZodTypeAny, "passthrough">>>;
|
|
1663
1674
|
confidenceOverride: z.ZodOptional<z.ZodObject<{
|
|
1664
1675
|
threshold: z.ZodNumber;
|
|
@@ -1776,6 +1787,7 @@ export declare const SynthesizePolicyInputSchema: z.ZodDiscriminatedUnion<"sourc
|
|
|
1776
1787
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1777
1788
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1778
1789
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1790
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1779
1791
|
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
1780
1792
|
confidenceOverride?: {
|
|
1781
1793
|
threshold: number;
|
|
@@ -1839,6 +1851,7 @@ export declare const SynthesizePolicyInputSchema: z.ZodDiscriminatedUnion<"sourc
|
|
|
1839
1851
|
validUntilLedger: z.ZodOptional<z.ZodNumber>;
|
|
1840
1852
|
limitAmount: z.ZodOptional<z.ZodString>;
|
|
1841
1853
|
invocationLimit: z.ZodOptional<z.ZodNumber>;
|
|
1854
|
+
swapRecipientAllowlist: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
1842
1855
|
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
1843
1856
|
confidenceOverride?: {
|
|
1844
1857
|
threshold: number;
|
package/dist-cjs/run/schemas.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.ToolErrorSchema = exports.SynthesizePolicyInputSchema = exports.SynthesizePolicyRecordingInputSchema = exports.InterpreterOptionsSchema = exports.SynthesizePolicyMandateInputSchema = exports.RecordTransactionInputSchema = exports.OzAdapterConfigSchema = exports.ComposeUserResponsesSchema = exports.MandateSpecSchema = exports.RecordedTransactionSchema = exports.ParseConfidenceSchema = exports.OnChainEventSchema = exports.TokenMovementSchema = exports.ContractInvocationSchema = exports.ScValSchema = exports.NetworkSchema = void 0;
|
|
20
20
|
const zod_1 = require("zod");
|
|
21
|
+
const address_ts_1 = require("../synth/address.js");
|
|
21
22
|
/** Soroban `valid_until` is a u32 ledger sequence; a value above this cannot be
|
|
22
23
|
* installed on-chain, so reject it at the boundary (fail-closed). */
|
|
23
24
|
const U32_MAX = 4294967295;
|
|
@@ -129,6 +130,13 @@ exports.ComposeUserResponsesSchema = zod_1.z
|
|
|
129
130
|
.regex(/^[0-9]+$/)
|
|
130
131
|
.optional(),
|
|
131
132
|
invocationLimit: zod_1.z.number().int().positive().optional(),
|
|
133
|
+
// Swap recipient allowlist (SoroSwap call_arg[3]). Each entry must be a
|
|
134
|
+
// Stellar address (G... wallet or C... contract); supplying it REPLACES the
|
|
135
|
+
// default pin to the recorded recipient. Validated with the shared StrKey
|
|
136
|
+
// helper (no hand-rolled regex).
|
|
137
|
+
swapRecipientAllowlist: zod_1.z
|
|
138
|
+
.array(zod_1.z.string().refine(address_ts_1.isStellarAddress, 'must be a Stellar address (G... or C...)'))
|
|
139
|
+
.optional(),
|
|
132
140
|
})
|
|
133
141
|
.passthrough();
|
|
134
142
|
/** OzAdapterConfig - the per-network OZ built-in instance addresses. */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** True when `s` is a valid Stellar address strkey - either an Ed25519 public
|
|
2
|
+
* key (`G...`) or a contract address (`C...`). Backed by the SDK's `StrKey`
|
|
3
|
+
* decoder (the same one the recorder uses in `record/decode.ts`); no
|
|
4
|
+
* hand-rolled regex. Used to validate a caller-supplied swap recipient
|
|
5
|
+
* allowlist, whose entries may be either a wallet (`G...`) or a contract
|
|
6
|
+
* (`C...`). */
|
|
7
|
+
export declare function isStellarAddress(s: string): boolean;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/synth/address.ts - Stellar address validation shared by the CLI + the
|
|
3
|
+
// run-layer Zod schema.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.isStellarAddress = isStellarAddress;
|
|
6
|
+
const stellar_sdk_1 = require("@stellar/stellar-sdk");
|
|
7
|
+
/** True when `s` is a valid Stellar address strkey - either an Ed25519 public
|
|
8
|
+
* key (`G...`) or a contract address (`C...`). Backed by the SDK's `StrKey`
|
|
9
|
+
* decoder (the same one the recorder uses in `record/decode.ts`); no
|
|
10
|
+
* hand-rolled regex. Used to validate a caller-supplied swap recipient
|
|
11
|
+
* allowlist, whose entries may be either a wallet (`G...`) or a contract
|
|
12
|
+
* (`C...`). */
|
|
13
|
+
function isStellarAddress(s) {
|
|
14
|
+
return stellar_sdk_1.StrKey.isValidEd25519PublicKey(s) || stellar_sdk_1.StrKey.isValidContract(s);
|
|
15
|
+
}
|
|
@@ -28,9 +28,10 @@ export interface ComposeUserResponses {
|
|
|
28
28
|
* entries on the same asset emit multiple leaves. */
|
|
29
29
|
oraclePriceBound?: OraclePriceBound[];
|
|
30
30
|
/** Recipient allowlist for a swap (call_arg[3] on SoroSwap's
|
|
31
|
-
* swap_exact_tokens_for_tokens).
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* swap_exact_tokens_for_tokens). When supplied, it REPLACES the default
|
|
32
|
+
* pin. Absent -> the recipient is pinned to the recorded value (mirroring
|
|
33
|
+
* SEP-41) and RECIPIENT_ALLOWLIST_EMPTY is surfaced as informational, never
|
|
34
|
+
* a silent free pass. */
|
|
34
35
|
swapRecipientAllowlist?: string[];
|
|
35
36
|
}
|
|
36
37
|
/** Composition options. */
|
|
@@ -311,11 +311,16 @@ function appendProtocolSpecificConstraints(ozConstraints, interpreterConstraints
|
|
|
311
311
|
}
|
|
312
312
|
// Swap recipient (call_arg[3]): when the caller supplies
|
|
313
313
|
// swapRecipientAllowlist, emit it as an `in` constraint on the recipient
|
|
314
|
-
// arg. When absent,
|
|
315
|
-
//
|
|
316
|
-
//
|
|
317
|
-
//
|
|
318
|
-
//
|
|
314
|
+
// arg. When absent, PIN the recipient to the recorded value - mirroring the
|
|
315
|
+
// SEP-41 `to` arg above: the recorded flow went to exactly one recipient, so
|
|
316
|
+
// pinning it is the minimal policy that permits exactly that flow. Leaving
|
|
317
|
+
// it unconstrained would permit an arbitrary recipient (an evil twin with
|
|
318
|
+
// call_arg[3] = attacker_wallet). RECIPIENT_ALLOWLIST_EMPTY is still
|
|
319
|
+
// surfaced, but as INFORMATIONAL (the recipient was pinned; here is how to
|
|
320
|
+
// widen it), never as a silent free pass. The recipient is only enforceable
|
|
321
|
+
// via the interpreter predicate, so the pin (and the ambiguity) apply only
|
|
322
|
+
// when the interpreter is enabled - otherwise the swap recipient is ignored
|
|
323
|
+
// (today's behaviour, matching the other SoroSwap constraints).
|
|
319
324
|
const recipientArg = topLevel.args[3];
|
|
320
325
|
if (swapRecipientAllowlist && swapRecipientAllowlist.length > 0) {
|
|
321
326
|
const cond = {
|
|
@@ -331,9 +336,14 @@ function appendProtocolSpecificConstraints(ozConstraints, interpreterConstraints
|
|
|
331
336
|
}
|
|
332
337
|
}
|
|
333
338
|
else if (interpreterEnabled && recipientArg && recipientArg.type === 'address') {
|
|
339
|
+
interpreterConstraints.push({
|
|
340
|
+
op: 'in',
|
|
341
|
+
selector: { kind: 'arg', argIndex: 3, scalarType: 'address' },
|
|
342
|
+
values: [recipientArg.value],
|
|
343
|
+
});
|
|
334
344
|
ambiguities.push({
|
|
335
345
|
code: 'RECIPIENT_ALLOWLIST_EMPTY',
|
|
336
|
-
question: `
|
|
346
|
+
question: `No recipient allowlist supplied; the swap recipient (call_arg[3]) was pinned to the recorded value ${recipientArg.value}. To permit additional recipients, supply an allowlist (CLI: --recipient <C...|G...>, repeatable; MCP: userResponses.swapRecipientAllowlist) - it REPLACES this default pin.`,
|
|
337
347
|
});
|
|
338
348
|
}
|
|
339
349
|
}
|
|
@@ -8,5 +8,15 @@ export interface GeneratedCases {
|
|
|
8
8
|
permit: EvalContext;
|
|
9
9
|
denies: DenyCase[];
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
declare const OVERPERMISSIVE_DIMENSIONS: readonly ["argument_reorder"];
|
|
12
|
+
declare const ORIGINAL_DIMENSIONS: string[];
|
|
13
|
+
export { ORIGINAL_DIMENSIONS, OVERPERMISSIVE_DIMENSIONS };
|
|
14
|
+
/** Build deterministic model-evaluated alternatives without mutating the intended call.
|
|
15
|
+
*
|
|
16
|
+
* @param predicate - the synthesized predicate
|
|
17
|
+
* @param permitCtx - EvalContext for the intended (permitted) call
|
|
18
|
+
* @param dimensions - optional whitelist of dimension names to emit; when omitted
|
|
19
|
+
* all known dimensions (including over-permissiveness mutations)
|
|
20
|
+
* are emitted. The synth pipeline passes ORIGINAL_DIMENSIONS so
|
|
21
|
+
* existing fixtures do not regress. */
|
|
22
|
+
export declare function generateCases(predicate: PredicateNode, permitCtx: EvalContext, dimensions?: string[]): GeneratedCases;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OVERPERMISSIVE_DIMENSIONS = exports.ORIGINAL_DIMENSIONS = void 0;
|
|
3
4
|
exports.generateCases = generateCases;
|
|
4
5
|
const types_ts_1 = require("../types.js");
|
|
5
6
|
const predicate_literals_ts_1 = require("./predicate-literals.js");
|
|
@@ -14,8 +15,39 @@ const ADJACENT_ASSETS = [
|
|
|
14
15
|
'CAS3J7GYLGXMF6TDJ5WQ2PEN4GRVNXJUIQ2TZU3ZB3OQ2V4DRCWI7WPF',
|
|
15
16
|
'CCWCLTASNDT57N3BCHOSVB5QWMV5URK4BXLDDF6ZZQYMBQ4OKZA3ZB2N',
|
|
16
17
|
];
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// Phase 1 property-harness mutation dimensions excluded from the synth pipeline's
|
|
19
|
+
// self-verify call so existing fixtures still emit policies. The harness tests
|
|
20
|
+
// them as FINDINGS against the already-emitted policy.
|
|
21
|
+
const OVERPERMISSIVE_DIMENSIONS = ['argument_reorder'];
|
|
22
|
+
exports.OVERPERMISSIVE_DIMENSIONS = OVERPERMISSIVE_DIMENSIONS;
|
|
23
|
+
// The 15 dimensions the synth pipeline uses for self-verify and minimise.
|
|
24
|
+
const ORIGINAL_DIMENSIONS = [
|
|
25
|
+
'amount',
|
|
26
|
+
'asset',
|
|
27
|
+
'contract',
|
|
28
|
+
'function',
|
|
29
|
+
'timing',
|
|
30
|
+
'time_window',
|
|
31
|
+
'invocation_count',
|
|
32
|
+
'arg_amount_bound',
|
|
33
|
+
'arg_bound',
|
|
34
|
+
'scope_contract_fn_arg',
|
|
35
|
+
'oracle_stale',
|
|
36
|
+
'oracle_missing',
|
|
37
|
+
'oracle_deviation_exceeded',
|
|
38
|
+
'oracle_paused',
|
|
39
|
+
'soroswap_allowed_path',
|
|
40
|
+
];
|
|
41
|
+
exports.ORIGINAL_DIMENSIONS = ORIGINAL_DIMENSIONS;
|
|
42
|
+
/** Build deterministic model-evaluated alternatives without mutating the intended call.
|
|
43
|
+
*
|
|
44
|
+
* @param predicate - the synthesized predicate
|
|
45
|
+
* @param permitCtx - EvalContext for the intended (permitted) call
|
|
46
|
+
* @param dimensions - optional whitelist of dimension names to emit; when omitted
|
|
47
|
+
* all known dimensions (including over-permissiveness mutations)
|
|
48
|
+
* are emitted. The synth pipeline passes ORIGINAL_DIMENSIONS so
|
|
49
|
+
* existing fixtures do not regress. */
|
|
50
|
+
function generateCases(predicate, permitCtx, dimensions) {
|
|
19
51
|
const facts = inspectPredicate(predicate);
|
|
20
52
|
const denies = [];
|
|
21
53
|
for (const comparison of facts.comparisons) {
|
|
@@ -147,6 +179,32 @@ function generateCases(predicate, permitCtx) {
|
|
|
147
179
|
ctx.args[comparison.left.index] = differentVector(ctx.args[comparison.left.index]);
|
|
148
180
|
denies.push({ dimension: 'soroswap_allowed_path', ctx });
|
|
149
181
|
}
|
|
182
|
+
// --- argument_reorder: swap first two address args ---
|
|
183
|
+
// Skipped when dimensions filter is active so the synth pipeline can emit a policy;
|
|
184
|
+
// the over-permissiveness harness then tests this mutation as a FINDING.
|
|
185
|
+
const constrainedArgIndices = new Set();
|
|
186
|
+
for (const comparison of facts.comparisons) {
|
|
187
|
+
if (comparison.left.kind === 'call_arg')
|
|
188
|
+
constrainedArgIndices.add(comparison.left.index);
|
|
189
|
+
}
|
|
190
|
+
for (const membership of facts.memberships) {
|
|
191
|
+
if (membership.needle.kind === 'call_arg')
|
|
192
|
+
constrainedArgIndices.add(membership.needle.index);
|
|
193
|
+
}
|
|
194
|
+
const hasConstrainedArg = constrainedArgIndices.size > 0;
|
|
195
|
+
if ((!dimensions || dimensions.includes('argument_reorder')) &&
|
|
196
|
+
hasConstrainedArg &&
|
|
197
|
+
permitCtx.args.length >= 2 &&
|
|
198
|
+
permitCtx.args[0]?.type === 'address' &&
|
|
199
|
+
permitCtx.args[1]?.type === 'address') {
|
|
200
|
+
const ctx = cloneContext(permitCtx);
|
|
201
|
+
// Guard above guarantees args[0] and args[1] exist and are address-typed.
|
|
202
|
+
const a0 = ctx.args[0];
|
|
203
|
+
const a1 = ctx.args[1];
|
|
204
|
+
ctx.args[0] = a1;
|
|
205
|
+
ctx.args[1] = a0;
|
|
206
|
+
denies.push({ dimension: 'argument_reorder', ctx });
|
|
207
|
+
}
|
|
150
208
|
// Version mismatch, malformed predicates, master authorization, and nonce replay are install-time checks and are intentionally omitted from model-evaluated cases.
|
|
151
209
|
return { permit: cloneContext(permitCtx), denies };
|
|
152
210
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { isStellarAddress } from './address.ts';
|
|
1
2
|
export { type ComposeOptions, type ComposeResult, composeFromRecording, } from './compose-from-recording.ts';
|
|
2
3
|
export { type DenyCase, type GeneratedCases, generateCases, } from './deny-cases.ts';
|
|
3
4
|
export { type EvalContext, type EvalResult, evaluate } from './evaluate.ts';
|
package/dist-cjs/synth/index.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/synth/index.ts - re-export the synthesizer front-ends.
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.synthesizeFromRecording = exports.synthesizeFromMandate = exports.scopeToContextRuleType = exports.decideScope = exports.minimize = exports.lower = exports.runHarness = exports.evaluate = exports.generateCases = exports.composeFromRecording = void 0;
|
|
4
|
+
exports.synthesizeFromRecording = exports.synthesizeFromMandate = exports.scopeToContextRuleType = exports.decideScope = exports.minimize = exports.lower = exports.runHarness = exports.evaluate = exports.generateCases = exports.composeFromRecording = exports.isStellarAddress = void 0;
|
|
5
|
+
// Exported because `swapRecipientAllowlist` is part of the public synthesis
|
|
6
|
+
// input: a caller assembling one needs the same validator the schema applies,
|
|
7
|
+
// and re-deriving it elsewhere means a second address check that can drift.
|
|
8
|
+
var address_ts_1 = require("./address.js");
|
|
9
|
+
Object.defineProperty(exports, "isStellarAddress", { enumerable: true, get: function () { return address_ts_1.isStellarAddress; } });
|
|
5
10
|
var compose_from_recording_ts_1 = require("./compose-from-recording.js");
|
|
6
11
|
Object.defineProperty(exports, "composeFromRecording", { enumerable: true, get: function () { return compose_from_recording_ts_1.composeFromRecording; } });
|
|
7
12
|
var deny_cases_ts_1 = require("./deny-cases.js");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { PredicateNode } from '../types.ts';
|
|
2
2
|
import type { EvalContext } from './evaluate.ts';
|
|
3
3
|
/** Remove top-level conjuncts only when the current and regenerated batteries still deny. */
|
|
4
|
-
export declare function minimize(predicate: PredicateNode, permitCtx: EvalContext): PredicateNode;
|
|
4
|
+
export declare function minimize(predicate: PredicateNode, permitCtx: EvalContext, dimensions?: string[]): PredicateNode;
|
|
@@ -4,17 +4,17 @@ exports.minimize = minimize;
|
|
|
4
4
|
const deny_cases_ts_1 = require("./deny-cases.js");
|
|
5
5
|
const harness_ts_1 = require("./harness.js");
|
|
6
6
|
/** Remove top-level conjuncts only when the current and regenerated batteries still deny. */
|
|
7
|
-
function minimize(predicate, permitCtx) {
|
|
7
|
+
function minimize(predicate, permitCtx, dimensions) {
|
|
8
8
|
if (predicate.op !== 'and')
|
|
9
9
|
return predicate;
|
|
10
10
|
let children = [...predicate.children];
|
|
11
11
|
let index = 0;
|
|
12
12
|
while (index < children.length) {
|
|
13
13
|
const current = { op: 'and', children };
|
|
14
|
-
const currentCases = (0, deny_cases_ts_1.generateCases)(current, permitCtx);
|
|
14
|
+
const currentCases = (0, deny_cases_ts_1.generateCases)(current, permitCtx, dimensions);
|
|
15
15
|
const candidateChildren = children.filter((_, childIndex) => childIndex !== index);
|
|
16
16
|
const candidate = { op: 'and', children: candidateChildren };
|
|
17
|
-
const candidateCases = (0, deny_cases_ts_1.generateCases)(candidate, permitCtx);
|
|
17
|
+
const candidateCases = (0, deny_cases_ts_1.generateCases)(candidate, permitCtx, dimensions);
|
|
18
18
|
const verificationCases = {
|
|
19
19
|
permit: candidateCases.permit,
|
|
20
20
|
denies: mergeDenyCases(currentCases.denies, candidateCases.denies),
|
|
@@ -130,6 +130,31 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
|
|
|
130
130
|
},
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
|
+
// 1a. Zero-invocation refusal (item 1). A recording with zero contract
|
|
134
|
+
// invocations cleared the parseConfidence gate legitimately (the
|
|
135
|
+
// `denom === 0` short-circuit pins overall to 1.0 for that case), but a
|
|
136
|
+
// policy must scope to an authorized contract call. Refuse before any
|
|
137
|
+
// lower/scope work so the failure is specific and actionable. The
|
|
138
|
+
// recorder's silence is also made visible via `parseConfidence.noInvocations`
|
|
139
|
+
// so consumers can pattern-match without inferring from `invocations: []`
|
|
140
|
+
// next to `overall: 1.0`. The message does NOT ask for an ABI - the
|
|
141
|
+
// failure mode is the recording shape, not decoding coverage.
|
|
142
|
+
const hasNoInvocations = tx.parseConfidence.noInvocations === true || tx.invocations.length === 0;
|
|
143
|
+
if (hasNoInvocations) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
error: {
|
|
147
|
+
code: 'SYNTHESIS_ERROR',
|
|
148
|
+
message: 'Recording contains no contract invocation to scope a policy to. Re-record a transaction that invokes a Soroban contract function (e.g. an SAC/SEP-41 transfer, a Blend yield-claim, or a SoroSwap swap).',
|
|
149
|
+
severity: 'error',
|
|
150
|
+
retryable: false,
|
|
151
|
+
details: {
|
|
152
|
+
invocations: tx.invocations.length,
|
|
153
|
+
noInvocationsMarker: tx.parseConfidence.noInvocations === true,
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
133
158
|
// Bound the recording size fail-closed (defense-in-depth for direct callers;
|
|
134
159
|
// the MCP schema caps this too).
|
|
135
160
|
if (tx.invocations.length > types_ts_1.SOROBAN_LIMITS.maxInvocations) {
|
|
@@ -321,8 +346,10 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
|
|
|
321
346
|
};
|
|
322
347
|
}
|
|
323
348
|
const permitCtx = buildPermitContext(tx, scope, topLevel, opts.userResponses, startingPredicate);
|
|
324
|
-
const finalPredicate = startingPredicate.op === 'and'
|
|
325
|
-
|
|
349
|
+
const finalPredicate = startingPredicate.op === 'and'
|
|
350
|
+
? (0, minimize_ts_1.minimize)(startingPredicate, permitCtx, deny_cases_ts_1.ORIGINAL_DIMENSIONS)
|
|
351
|
+
: startingPredicate;
|
|
352
|
+
const harnessCases = (0, deny_cases_ts_1.generateCases)(finalPredicate, permitCtx, deny_cases_ts_1.ORIGINAL_DIMENSIONS);
|
|
326
353
|
const harnessResult = (0, harness_ts_1.runHarness)(finalPredicate, harnessCases);
|
|
327
354
|
if (!harnessResult.ok) {
|
|
328
355
|
return {
|
|
@@ -423,12 +450,24 @@ function synthesizeFromRecordingInner(tx, opts, ozConfig) {
|
|
|
423
450
|
...compileRes.proposed.contextRule,
|
|
424
451
|
policies: mergedRefs,
|
|
425
452
|
};
|
|
453
|
+
// When nothing installable was synthesised (no interpreter doc AND no OZ
|
|
454
|
+
// policy refs), an empty `policies` array reads as "no restrictions" rather
|
|
455
|
+
// than "I synthesised nothing". Surface that explicitly so the empty result
|
|
456
|
+
// is never mistaken for a permissive policy - the context rule still exists,
|
|
457
|
+
// but it constrains nothing. (Kept as `{ok:true}` so the documented
|
|
458
|
+
// Path-A/Path-B demo behaviour is preserved - see F3.)
|
|
459
|
+
const zeroPolicyWarning = mergedRefs.length === 0 && !interpreterPolicyDocument
|
|
460
|
+
? [
|
|
461
|
+
'No policy constraints were synthesised: the call to this contract is UNCONSTRAINED by this policy. Enable the interpreter (supply a smart account) to enforce the surfaced constraints.',
|
|
462
|
+
]
|
|
463
|
+
: [];
|
|
426
464
|
const proposed = {
|
|
427
465
|
contextRule: mergedContextRule,
|
|
428
466
|
policyDocuments: interpreterPolicyDocument ? [interpreterPolicyDocument] : [],
|
|
429
467
|
policyRefs: mergedRefs,
|
|
430
468
|
parseConfidence: { ...tx.parseConfidence },
|
|
431
469
|
warnings: [
|
|
470
|
+
...zeroPolicyWarning,
|
|
432
471
|
...ozUncovered.map((u) => `${UNCOVERED_PREFIX}${u}`),
|
|
433
472
|
...composed.warnings.map((w) => `${UNCOVERED_PREFIX}${w}`),
|
|
434
473
|
],
|
|
@@ -474,7 +513,7 @@ function validateOptions(opts) {
|
|
|
474
513
|
return synthesisError(`invocationLimit must be a positive integer, got: ${ur.invocationLimit}`);
|
|
475
514
|
}
|
|
476
515
|
if (ur.limitAmount !== undefined && !isPositiveI128(ur.limitAmount)) {
|
|
477
|
-
return synthesisError(`limitAmount must be a positive i128 decimal string, got: ${ur.limitAmount}`);
|
|
516
|
+
return synthesisError(`limitAmount must be a positive i128 decimal string within [1, ${I128_MAX}] (2^127-1), got: ${ur.limitAmount}`);
|
|
478
517
|
}
|
|
479
518
|
}
|
|
480
519
|
if (opts.interpreter) {
|
|
@@ -524,15 +563,24 @@ function validateOptions(opts) {
|
|
|
524
563
|
* contract address derivable from the on-chain account, never a
|
|
525
564
|
* fixture/LLM-seam marker. */
|
|
526
565
|
const PLACEHOLDER_SMART_ACCOUNT_PREFIX = /^(VERIFY-|PLACEHOLDER-|TODO-)/i;
|
|
566
|
+
/** Maximum value a signed i128 can hold (2^127-1). A limitAmount above this
|
|
567
|
+
* cannot be represented on-chain, so reject it at the synthesis boundary
|
|
568
|
+
* (fail-closed) instead of passing it through as an over-broad spending_limit.
|
|
569
|
+
* Mirrors the SOROBAN_LIMITS.u32Max bound the ledger-sequence fields enforce. */
|
|
570
|
+
const I128_MAX = 2n ** 127n - 1n;
|
|
527
571
|
function isPositiveInt(n) {
|
|
528
572
|
return Number.isInteger(n) && n > 0;
|
|
529
573
|
}
|
|
530
|
-
/** True when `s` is a canonical positive decimal integer
|
|
574
|
+
/** True when `s` is a canonical positive decimal integer inside the signed-i128
|
|
575
|
+
* range [1, 2^127-1]. A value above the i128 ceiling is rejected (fail-closed):
|
|
576
|
+
* it cannot be installed on-chain, and accepting it would emit a spending_limit
|
|
577
|
+
* with an effectively unbounded cap. */
|
|
531
578
|
function isPositiveI128(s) {
|
|
532
579
|
if (!/^[0-9]+$/.test(s))
|
|
533
580
|
return false;
|
|
534
581
|
try {
|
|
535
|
-
|
|
582
|
+
const v = BigInt(s);
|
|
583
|
+
return v > 0n && v <= I128_MAX;
|
|
536
584
|
}
|
|
537
585
|
catch {
|
|
538
586
|
return false;
|
package/dist-cjs/types.d.ts
CHANGED
|
@@ -21,6 +21,17 @@ export type ScVal = {
|
|
|
21
21
|
} | {
|
|
22
22
|
type: 'bytes';
|
|
23
23
|
value: string;
|
|
24
|
+
}
|
|
25
|
+
/** Map<Symbol, ScVal> carried forward for SAC/SEP-41 transfer event data,
|
|
26
|
+
* which uses a Map shape (key "amount" -> I128) on Stellar mainnet. The
|
|
27
|
+
* entry key is captured as its symbol/form string so readAmount can route
|
|
28
|
+
* to the conventional field name without re-decoding the full XDR. */
|
|
29
|
+
| {
|
|
30
|
+
type: 'map';
|
|
31
|
+
value: Array<{
|
|
32
|
+
key: string;
|
|
33
|
+
val: ScVal;
|
|
34
|
+
}>;
|
|
24
35
|
} | {
|
|
25
36
|
type: 'other';
|
|
26
37
|
value: string;
|
|
@@ -272,7 +283,15 @@ export interface AmbiguityPrompt {
|
|
|
272
283
|
export type AmbiguityCode = 'DURATION_UNSPECIFIED' | 'AMOUNT_BOUND_MISSING' | 'RECIPIENT_ALLOWLIST_EMPTY' | 'FREQUENCY_BOUND_MISSING' | 'ORACLE_ASSET_UNKNOWN' | 'MULTIPLE_UNRELATED_TARGETS';
|
|
273
284
|
/** Structured recording freshness. `overall` is the gate threshold (default 1.0); the
|
|
274
285
|
* breakdown is the diagnostic the user / agent sees when the gate fires. The
|
|
275
|
-
* computation rule is pinned: `overall = 1 - (unknownContracts + opaqueScVals) / total`.
|
|
286
|
+
* computation rule is pinned: `overall = 1 - (unknownContracts + opaqueScVals) / total`.
|
|
287
|
+
*
|
|
288
|
+
* `noInvocations` (item 1) is a recorder-side marker set ONLY when the envelope
|
|
289
|
+
* contained zero `invokeContract` host functions (e.g. a `createContract` or
|
|
290
|
+
* `uploadContractWasm` op). The math is unchanged: that case still scores 1.0
|
|
291
|
+
* via the `denom === 0` guard. The marker exists so a consumer does not have
|
|
292
|
+
* to infer the situation from `invocations.length === 0` next to `overall: 1.0`
|
|
293
|
+
* — a downstream synth refuses such recordings with a specific, actionable
|
|
294
|
+
* error rather than silently producing a useless empty scope. */
|
|
276
295
|
export interface ParseConfidence {
|
|
277
296
|
overall: number;
|
|
278
297
|
knownContracts: string[];
|
|
@@ -285,4 +304,8 @@ export interface ParseConfidence {
|
|
|
285
304
|
type: string;
|
|
286
305
|
}>;
|
|
287
306
|
thresholdUsed: number;
|
|
307
|
+
/** True iff the recorder decoded zero contract invocations. Absent on
|
|
308
|
+
* recordings that pre-date this field; consumers should treat `undefined`
|
|
309
|
+
* as "no marker" and rely on `invocations.length` as the fallback. */
|
|
310
|
+
noInvocations?: true;
|
|
288
311
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crediolabs/policy-synth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Off-chain TypeScript synthesis core for the OZ Accounts Policy Builder. Records Soroban transactions, synthesises the minimal policy that permits exactly that flow, verifies it, and returns an unsigned install transaction.",
|
|
6
6
|
"type": "module",
|