@crediolabs/policy-synth 0.1.4 → 0.1.5
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/LICENSE +21 -0
- package/dist/codegen/template.js +3 -3
- package/dist/record/decode.d.ts +6 -3
- package/dist/record/decode.js +13 -3
- package/dist/run/index.d.ts +31 -0
- package/dist/run/index.js +164 -0
- package/dist/run/schemas.d.ts +2003 -0
- package/dist/run/schemas.js +215 -0
- package/dist/synth/deny-cases.js +18 -2
- package/dist/synth/evaluate.js +2 -2
- package/dist/synth/synthesize-from-recording.d.ts +5 -2
- package/dist/synth/synthesize-from-recording.js +121 -63
- package/dist/types.d.ts +17 -0
- package/dist/types.js +17 -0
- package/dist/verify/simulate.js +18 -2
- package/dist/verify/verify.js +18 -2
- package/dist-cjs/adapters/interpreter/adapter.d.ts +38 -0
- package/dist-cjs/adapters/interpreter/adapter.js +527 -0
- package/dist-cjs/adapters/interpreter/index.d.ts +1 -0
- package/dist-cjs/adapters/interpreter/index.js +8 -0
- package/dist-cjs/adapters/oz/adapter.d.ts +20 -0
- package/dist-cjs/adapters/oz/adapter.js +289 -0
- package/dist-cjs/adapters/oz/index.d.ts +1 -0
- package/dist-cjs/adapters/oz/index.js +8 -0
- package/dist-cjs/codegen/compile-gate.d.ts +33 -0
- package/dist-cjs/codegen/compile-gate.js +123 -0
- package/dist-cjs/codegen/index.d.ts +2 -0
- package/dist-cjs/codegen/index.js +14 -0
- package/dist-cjs/codegen/template.d.ts +18 -0
- package/dist-cjs/codegen/template.js +134 -0
- package/dist-cjs/errors.d.ts +37 -0
- package/dist-cjs/errors.js +3 -0
- package/dist-cjs/index.d.ts +11 -0
- package/dist-cjs/index.js +27 -0
- package/dist-cjs/ir/index.d.ts +1 -0
- package/dist-cjs/ir/index.js +3 -0
- package/dist-cjs/ir/types.d.ts +108 -0
- package/dist-cjs/ir/types.js +12 -0
- package/dist-cjs/mandate/index.d.ts +2 -0
- package/dist-cjs/mandate/index.js +6 -0
- package/dist-cjs/mandate/to-ir.d.ts +3 -0
- package/dist-cjs/mandate/to-ir.js +63 -0
- package/dist-cjs/mandate/types.d.ts +20 -0
- package/dist-cjs/mandate/types.js +9 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/predicate/encode.d.ts +10 -0
- package/dist-cjs/predicate/encode.js +252 -0
- package/dist-cjs/predicate/index.d.ts +1 -0
- package/dist-cjs/predicate/index.js +6 -0
- package/dist-cjs/record/decode.d.ts +79 -0
- package/dist-cjs/record/decode.js +398 -0
- package/dist-cjs/record/freshness.d.ts +17 -0
- package/dist-cjs/record/freshness.js +55 -0
- package/dist-cjs/record/index.d.ts +21 -0
- package/dist-cjs/record/index.js +166 -0
- package/dist-cjs/record/movements.d.ts +20 -0
- package/dist-cjs/record/movements.js +192 -0
- package/dist-cjs/record/rpc.d.ts +22 -0
- package/dist-cjs/record/rpc.js +74 -0
- package/dist-cjs/record/validate.d.ts +22 -0
- package/dist-cjs/record/validate.js +63 -0
- package/dist-cjs/registry/identify.d.ts +11 -0
- package/dist-cjs/registry/identify.js +87 -0
- package/dist-cjs/registry/index.d.ts +3 -0
- package/dist-cjs/registry/index.js +15 -0
- package/dist-cjs/registry/known-addresses.d.ts +16 -0
- package/dist-cjs/registry/known-addresses.js +53 -0
- package/dist-cjs/registry/protocols.d.ts +38 -0
- package/dist-cjs/registry/protocols.js +153 -0
- package/dist-cjs/review-card/builder.d.ts +14 -0
- package/dist-cjs/review-card/builder.js +264 -0
- package/dist-cjs/review-card/conflict.d.ts +40 -0
- package/dist-cjs/review-card/conflict.js +114 -0
- package/dist-cjs/review-card/cross-check.d.ts +11 -0
- package/dist-cjs/review-card/cross-check.js +151 -0
- package/dist-cjs/review-card/index.d.ts +3 -0
- package/dist-cjs/review-card/index.js +10 -0
- package/dist-cjs/run/index.d.ts +31 -0
- package/dist-cjs/run/index.js +178 -0
- package/dist-cjs/run/schemas.d.ts +2003 -0
- package/dist-cjs/run/schemas.js +218 -0
- package/dist-cjs/seams/index.d.ts +1 -0
- package/dist-cjs/seams/index.js +3 -0
- package/dist-cjs/seams/types.d.ts +66 -0
- package/dist-cjs/seams/types.js +12 -0
- package/dist-cjs/synth/compose-from-recording.d.ts +64 -0
- package/dist-cjs/synth/compose-from-recording.js +356 -0
- package/dist-cjs/synth/deny-cases.d.ts +12 -0
- package/dist-cjs/synth/deny-cases.js +380 -0
- package/dist-cjs/synth/evaluate.d.ts +39 -0
- package/dist-cjs/synth/evaluate.js +428 -0
- package/dist-cjs/synth/harness.d.ts +16 -0
- package/dist-cjs/synth/harness.js +29 -0
- package/dist-cjs/synth/index.d.ts +9 -0
- package/dist-cjs/synth/index.js +23 -0
- package/dist-cjs/synth/lower.d.ts +23 -0
- package/dist-cjs/synth/lower.js +119 -0
- package/dist-cjs/synth/minimize.d.ts +4 -0
- package/dist-cjs/synth/minimize.js +41 -0
- package/dist-cjs/synth/predicate-literals.d.ts +5 -0
- package/dist-cjs/synth/predicate-literals.js +28 -0
- package/dist-cjs/synth/scope.d.ts +26 -0
- package/dist-cjs/synth/scope.js +81 -0
- package/dist-cjs/synth/synthesize-from-mandate.d.ts +5 -0
- package/dist-cjs/synth/synthesize-from-mandate.js +37 -0
- package/dist-cjs/synth/synthesize-from-recording.d.ts +49 -0
- package/dist-cjs/synth/synthesize-from-recording.js +711 -0
- package/dist-cjs/types.d.ts +288 -0
- package/dist-cjs/types.js +55 -0
- package/dist-cjs/verify/envelope.d.ts +15 -0
- package/dist-cjs/verify/envelope.js +23 -0
- package/dist-cjs/verify/index.d.ts +3 -0
- package/dist-cjs/verify/index.js +8 -0
- package/dist-cjs/verify/simulate.d.ts +31 -0
- package/dist-cjs/verify/simulate.js +261 -0
- package/dist-cjs/verify/verify.d.ts +21 -0
- package/dist-cjs/verify/verify.js +192 -0
- package/package.json +43 -3
- package/src/codegen/template.ts +3 -3
- package/src/record/decode.ts +21 -10
- package/src/run/index.ts +213 -0
- package/src/run/schemas.ts +242 -0
- package/src/synth/deny-cases.ts +21 -2
- package/src/synth/evaluate.ts +2 -2
- package/src/synth/synthesize-from-recording.ts +141 -62
- package/src/types.ts +19 -0
- package/src/verify/simulate.ts +21 -2
- package/src/verify/verify.ts +21 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Untangled Finance Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/codegen/template.js
CHANGED
|
@@ -51,10 +51,10 @@ function renderUncovered(uncovered) {
|
|
|
51
51
|
*
|
|
52
52
|
* Same `(spec, name)` always produces byte-identical output. */
|
|
53
53
|
export function generateRust(spec, name) {
|
|
54
|
-
const
|
|
55
|
-
const
|
|
54
|
+
const _contract = spec.contract;
|
|
55
|
+
const _fnName = spec.fnName ?? 'enforce';
|
|
56
56
|
const structName = asRustIdent(name);
|
|
57
|
-
const
|
|
57
|
+
const _dataKeyName = `${asRustIdent(name)}_data`;
|
|
58
58
|
const uncoveredBlock = renderUncovered(spec.uncovered);
|
|
59
59
|
return `// Auto-generated by the OZ Policy Builder codegen escape hatch.
|
|
60
60
|
// THIS OUTPUT IS THE USER'S TO COMPLETE AND AUDIT.
|
package/dist/record/decode.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { xdr } from '@stellar/stellar-sdk';
|
|
2
|
-
import type
|
|
2
|
+
import { type ContractInvocation, type Network, type OnChainEvent, type ParseConfidence, type ScVal, type TokenMovement } from '../types.ts';
|
|
3
3
|
/** Result of decoding an envelope XDR plus the surrounding metadata from
|
|
4
4
|
* `getTransaction` (events + auth entries). All fields are plain JSON; the
|
|
5
5
|
* caller decides how to interpret them. */
|
|
@@ -44,8 +44,11 @@ export declare function decodeScAddressToC(scAddr: xdr.ScAddress): string;
|
|
|
44
44
|
/** Map an arbitrary ScVal into the normalised `ScVal` subset defined in
|
|
45
45
|
* types.ts. Anything outside the subset is wrapped as `{type:'other', value}`
|
|
46
46
|
* AND recorded in `opaqueScVals` so the freshness module can reduce
|
|
47
|
-
* parseConfidence.
|
|
48
|
-
|
|
47
|
+
* parseConfidence. Recursion is bounded by `MAX_SCVAL_DEPTH` so a
|
|
48
|
+
* hand-crafted nested-vec payload cannot RangeError the JS stack; the
|
|
49
|
+
* over-depth branch is collapsed to a single opaque `depth-exceeded` token
|
|
50
|
+
* (the recorder's parseConfidence gate then fails the recording closed). */
|
|
51
|
+
export declare function scValToSubset(val: xdr.ScVal, path: string, opaqueScVals: ParseConfidence['opaqueScVals'], depth?: number): ScVal;
|
|
49
52
|
/** Convert Int128Parts (hi: Int64, lo: Uint64) to a BigInt.
|
|
50
53
|
* hi is a signed 64-bit value; lo is unsigned. We use the SDK-provided
|
|
51
54
|
* `toString()` form which gives the signed decimal representation of the 64-bit
|
package/dist/record/decode.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// No network calls. No randomness. No globals.
|
|
18
18
|
import { Address, StrKey, xdr } from '@stellar/stellar-sdk';
|
|
19
19
|
import { identifyProtocol } from "../registry/identify.js";
|
|
20
|
+
import { MAX_SCVAL_DEPTH, } from "../types.js";
|
|
20
21
|
/** Decode a TransactionEnvelope XDR (base64) into a fully parsed invocation tree. */
|
|
21
22
|
export function decodeEnvelopeXdr(envelopeXdrB64, events = [], _authEntries = [], ledgerSequence = 0, knownContracts = new Set(), network = null) {
|
|
22
23
|
const envelope = xdr.TransactionEnvelope.fromXDR(envelopeXdrB64, 'base64');
|
|
@@ -226,8 +227,11 @@ function symbolValueFromBufferOrString(s) {
|
|
|
226
227
|
/** Map an arbitrary ScVal into the normalised `ScVal` subset defined in
|
|
227
228
|
* types.ts. Anything outside the subset is wrapped as `{type:'other', value}`
|
|
228
229
|
* AND recorded in `opaqueScVals` so the freshness module can reduce
|
|
229
|
-
* parseConfidence.
|
|
230
|
-
|
|
230
|
+
* parseConfidence. Recursion is bounded by `MAX_SCVAL_DEPTH` so a
|
|
231
|
+
* hand-crafted nested-vec payload cannot RangeError the JS stack; the
|
|
232
|
+
* over-depth branch is collapsed to a single opaque `depth-exceeded` token
|
|
233
|
+
* (the recorder's parseConfidence gate then fails the recording closed). */
|
|
234
|
+
export function scValToSubset(val, path, opaqueScVals, depth = 0) {
|
|
231
235
|
const kind = val.switch().name;
|
|
232
236
|
switch (kind) {
|
|
233
237
|
case 'scvAddress': {
|
|
@@ -252,10 +256,16 @@ export function scValToSubset(val, path, opaqueScVals) {
|
|
|
252
256
|
return { type: 'symbol', value: val.sym().toString() };
|
|
253
257
|
}
|
|
254
258
|
case 'scvVec': {
|
|
259
|
+
if (depth >= MAX_SCVAL_DEPTH) {
|
|
260
|
+
// Over-depth branch: collapse to a single opaque terminal so the
|
|
261
|
+
// downstream parseConfidence gate sees the fail-closed freshness hit.
|
|
262
|
+
opaqueScVals.push({ path, type: 'depth-exceeded' });
|
|
263
|
+
return { type: 'other', value: 'depth-exceeded' };
|
|
264
|
+
}
|
|
255
265
|
const arr = (val.vec() ?? []);
|
|
256
266
|
return {
|
|
257
267
|
type: 'vec',
|
|
258
|
-
value: arr.map((v, i) => scValToSubset(v, `${path}[${i}]`, opaqueScVals)),
|
|
268
|
+
value: arr.map((v, i) => scValToSubset(v, `${path}[${i}]`, opaqueScVals, depth + 1)),
|
|
259
269
|
};
|
|
260
270
|
}
|
|
261
271
|
case 'scvBytes': {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type ProposedPolicy, type RecordedTransaction, type ToolResponse } from '../index.ts';
|
|
2
|
+
import { type RecordTransactionInput, type SynthesizePolicyInput } from './schemas.ts';
|
|
3
|
+
export type { RecordTransactionInput, SynthesizePolicyInput } from './schemas.ts';
|
|
4
|
+
export { ComposeUserResponsesSchema, InterpreterOptionsSchema, MandateSpecSchema, NetworkSchema, OzAdapterConfigSchema, RecordedTransactionSchema, RecordTransactionInputSchema, SynthesizePolicyInputSchema, ToolErrorSchema, } from './schemas.ts';
|
|
5
|
+
export type RunRecordTransactionInput = RecordTransactionInput;
|
|
6
|
+
export type RunSynthesizePolicyInput = SynthesizePolicyInput;
|
|
7
|
+
/** `record_transaction` body - wraps `recordTransaction`. The tool input
|
|
8
|
+
* matches the core RecordInput minus the injected `fetcher` (the transport
|
|
9
|
+
* layer does not own the RPC). Returns the core ToolResponse unchanged.
|
|
10
|
+
*
|
|
11
|
+
* Item 6: a try/catch envelope wraps the core call. The core's own
|
|
12
|
+
* `recordTransaction` already returns `{ok:false, error}` for Zod + decoder
|
|
13
|
+
* failures, but a throw from the SDK (e.g. a malformed hash handed to
|
|
14
|
+
* `@stellar/stellar-sdk`'s StrKey decoder) would otherwise surface as
|
|
15
|
+
* "[object Object]" in the MCP transport. The envelope converts any
|
|
16
|
+
* non-ToolResponse throw into a structured `RECORDING_FAILED` ToolError so
|
|
17
|
+
* the agent always sees a machine-readable error code. */
|
|
18
|
+
export declare function runRecordTransaction(raw: unknown): Promise<ToolResponse<RecordedTransaction>>;
|
|
19
|
+
/** `synthesize_policy` body - discriminated union on `source`:
|
|
20
|
+
* - `mandate` -> synthesizeFromMandate
|
|
21
|
+
* - `recording` -> synthesizeFromRecording
|
|
22
|
+
* Exposing BOTH front-ends through ONE tool keeps the MCP surface tiny while
|
|
23
|
+
* letting the agent pick the deterministic or the inferred path. The CLI
|
|
24
|
+
* routes the same way.
|
|
25
|
+
*
|
|
26
|
+
* Item 6: same try/catch envelope as `runRecordTransaction`. The core
|
|
27
|
+
* `synthesizeFromRecording` already converts ToolError-shaped throws to
|
|
28
|
+
* `{ok:false, error}`, but a raw throw from the SDK (e.g. an unexpected
|
|
29
|
+
* XDR decode error in the adapter) would otherwise surface as
|
|
30
|
+
* "[object Object]" in the MCP transport. */
|
|
31
|
+
export declare function runSynthesizePolicy(raw: unknown): Promise<ToolResponse<ProposedPolicy>>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// packages/policy-synth/src/run/index.ts
|
|
2
|
+
//
|
|
3
|
+
// Tool-body adapters for the two core front-ends. Reachable as
|
|
4
|
+
// `@crediolabs/policy-synth/run` from both the MCP server (which re-exports
|
|
5
|
+
// for tool-registration glue) and the CLI (which calls them directly). Each
|
|
6
|
+
// body is a THIN adapter over the pure core:
|
|
7
|
+
//
|
|
8
|
+
// 1. Re-validate the parsed input via Zod. This is a defence-in-depth check -
|
|
9
|
+
// the SDK has already parsed it through the registered schema, but we never
|
|
10
|
+
// want a tool body to throw on garbage (the SDK treats uncaught throws as
|
|
11
|
+
// transport errors and the agent loses the machine-readable ToolError).
|
|
12
|
+
// 2. Dispatch to the matching core entry point with the minimum required
|
|
13
|
+
// inputs.
|
|
14
|
+
// 3. Return the core's ToolResponse<T> unchanged. The server layer maps it
|
|
15
|
+
// to the MCP envelope; nothing here knows about MCP.
|
|
16
|
+
//
|
|
17
|
+
// No business logic. No retries. No session state. The same call shape can
|
|
18
|
+
// drive the CLI (which calls into the same core directly without MCP).
|
|
19
|
+
import { placeholderOzConfig, recordTransaction, synthesizeFromMandate, synthesizeFromRecording, } from "../index.js";
|
|
20
|
+
import { RecordTransactionInputSchema, SynthesizePolicyInputSchema, } from "./schemas.js";
|
|
21
|
+
// Re-export the underlying Zod schemas so the MCP package (and any other
|
|
22
|
+
// downstream consumer) can import the canonical input shapes from the same
|
|
23
|
+
// module that owns the tool-body glue. The strict schemas are the source of
|
|
24
|
+
// truth - MCP tool shapes are derived from them.
|
|
25
|
+
export { ComposeUserResponsesSchema, InterpreterOptionsSchema, MandateSpecSchema, NetworkSchema, OzAdapterConfigSchema, RecordedTransactionSchema, RecordTransactionInputSchema, SynthesizePolicyInputSchema, ToolErrorSchema, } from "./schemas.js";
|
|
26
|
+
/** `record_transaction` body - wraps `recordTransaction`. The tool input
|
|
27
|
+
* matches the core RecordInput minus the injected `fetcher` (the transport
|
|
28
|
+
* layer does not own the RPC). Returns the core ToolResponse unchanged.
|
|
29
|
+
*
|
|
30
|
+
* Item 6: a try/catch envelope wraps the core call. The core's own
|
|
31
|
+
* `recordTransaction` already returns `{ok:false, error}` for Zod + decoder
|
|
32
|
+
* failures, but a throw from the SDK (e.g. a malformed hash handed to
|
|
33
|
+
* `@stellar/stellar-sdk`'s StrKey decoder) would otherwise surface as
|
|
34
|
+
* "[object Object]" in the MCP transport. The envelope converts any
|
|
35
|
+
* non-ToolResponse throw into a structured `RECORDING_FAILED` ToolError so
|
|
36
|
+
* the agent always sees a machine-readable error code. */
|
|
37
|
+
export async function runRecordTransaction(raw) {
|
|
38
|
+
const parsed = RecordTransactionInputSchema.safeParse(raw);
|
|
39
|
+
if (!parsed.success) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: validationError('record_transaction', parsed.error.issues),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const input = parsed.data;
|
|
46
|
+
// Strip the wire-only `confidenceOverride` and pass the rest straight through.
|
|
47
|
+
const coreInput = {
|
|
48
|
+
network: input.network,
|
|
49
|
+
...(input.hash !== undefined ? { hash: input.hash } : {}),
|
|
50
|
+
...(input.xdr !== undefined ? { xdr: input.xdr } : {}),
|
|
51
|
+
...(input.confidenceOverride !== undefined
|
|
52
|
+
? { confidenceOverride: input.confidenceOverride }
|
|
53
|
+
: {}),
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
return await recordTransaction(coreInput);
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: caughtError('record_transaction', 'RECORDING_FAILED', e),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** `synthesize_policy` body - discriminated union on `source`:
|
|
66
|
+
* - `mandate` -> synthesizeFromMandate
|
|
67
|
+
* - `recording` -> synthesizeFromRecording
|
|
68
|
+
* Exposing BOTH front-ends through ONE tool keeps the MCP surface tiny while
|
|
69
|
+
* letting the agent pick the deterministic or the inferred path. The CLI
|
|
70
|
+
* routes the same way.
|
|
71
|
+
*
|
|
72
|
+
* Item 6: same try/catch envelope as `runRecordTransaction`. The core
|
|
73
|
+
* `synthesizeFromRecording` already converts ToolError-shaped throws to
|
|
74
|
+
* `{ok:false, error}`, but a raw throw from the SDK (e.g. an unexpected
|
|
75
|
+
* XDR decode error in the adapter) would otherwise surface as
|
|
76
|
+
* "[object Object]" in the MCP transport. */
|
|
77
|
+
export async function runSynthesizePolicy(raw) {
|
|
78
|
+
const parsed = SynthesizePolicyInputSchema.safeParse(raw);
|
|
79
|
+
if (!parsed.success) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: validationError('synthesize_policy', parsed.error.issues),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const input = parsed.data;
|
|
86
|
+
const ozConfig = resolveOzConfig(input);
|
|
87
|
+
try {
|
|
88
|
+
if (input.source === 'mandate') {
|
|
89
|
+
// Zod's optional fields widen to `T | undefined`, which the core's
|
|
90
|
+
// exact-optional MandateSpec rejects; the schema already validated the
|
|
91
|
+
// shape, so assert it (same pattern as the recordedTx cast below).
|
|
92
|
+
return await synthesizeFromMandate(input.mandate, ozConfig);
|
|
93
|
+
}
|
|
94
|
+
// recording source
|
|
95
|
+
const recorded = input.recordedTx;
|
|
96
|
+
return await synthesizeFromRecording(recorded, {
|
|
97
|
+
network: input.network,
|
|
98
|
+
...(input.userResponses !== undefined ? { userResponses: input.userResponses } : {}),
|
|
99
|
+
...(input.confidenceOverride !== undefined
|
|
100
|
+
? { confidenceOverride: input.confidenceOverride }
|
|
101
|
+
: {}),
|
|
102
|
+
...(input.interpreter !== undefined ? { interpreter: input.interpreter } : {}),
|
|
103
|
+
}, ozConfig);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
error: caughtError('synthesize_policy', 'SYNTHESIS_ERROR', e),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function resolveOzConfig(input) {
|
|
113
|
+
if (input.ozConfig)
|
|
114
|
+
return input.ozConfig;
|
|
115
|
+
// The mandate path is network-agnostic; fall back to mainnet so the
|
|
116
|
+
// placeholder OZ instance addresses are deterministic.
|
|
117
|
+
return placeholderOzConfig('mainnet');
|
|
118
|
+
}
|
|
119
|
+
/** Build a canonical ToolError for a Zod validation failure. The remediation
|
|
120
|
+
* hint points the agent back at the right tool with an empty arg bag - the
|
|
121
|
+
* tool name IS the machine-readable hint. */
|
|
122
|
+
function validationError(toolName, issues) {
|
|
123
|
+
const code = toolName === 'record_transaction' ? 'RECORDING_FAILED' : 'SYNTHESIS_ERROR';
|
|
124
|
+
return {
|
|
125
|
+
code,
|
|
126
|
+
message: `${toolName}: invalid input: ${issues
|
|
127
|
+
.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`)
|
|
128
|
+
.join('; ')}`,
|
|
129
|
+
severity: 'error',
|
|
130
|
+
retryable: false,
|
|
131
|
+
remediation: {
|
|
132
|
+
toolCall: { name: toolName, args: {} },
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Build a canonical ToolError for a thrown exception caught by the tool
|
|
137
|
+
* envelope. We refuse to surface the raw exception's text (the MCP SDK
|
|
138
|
+
* stringifies thrown objects as "[object Object]"), so we extract a
|
|
139
|
+
* string-friendly message and tag the original error in `details` for the
|
|
140
|
+
* agent to inspect. The `code` is the tool's domain code (RECORDING_FAILED
|
|
141
|
+
* for `record_transaction`, SYNTHESIS_ERROR for `synthesize_policy`) so the
|
|
142
|
+
* agent dispatches on the same code the structured ToolError would carry. */
|
|
143
|
+
function caughtError(toolName, code, e) {
|
|
144
|
+
let message;
|
|
145
|
+
if (e instanceof Error) {
|
|
146
|
+
message = e.message || `${toolName}: unknown error`;
|
|
147
|
+
}
|
|
148
|
+
else if (typeof e === 'string') {
|
|
149
|
+
message = e;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
message = `${toolName}: caught non-Error throw of type ${typeof e}`;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
code,
|
|
156
|
+
message: `${toolName}: unhandled throw escaped core envelope: ${message}`,
|
|
157
|
+
severity: 'error',
|
|
158
|
+
retryable: false,
|
|
159
|
+
remediation: {
|
|
160
|
+
toolCall: { name: toolName, args: {} },
|
|
161
|
+
},
|
|
162
|
+
details: { thrown: String(e) },
|
|
163
|
+
};
|
|
164
|
+
}
|