@crediolabs/policy-synth 0.1.4 → 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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/dist/codegen/template.js +3 -3
  3. package/dist/record/decode.d.ts +6 -3
  4. package/dist/record/decode.js +56 -3
  5. package/dist/record/freshness.d.ts +14 -1
  6. package/dist/record/freshness.js +32 -2
  7. package/dist/record/index.d.ts +11 -0
  8. package/dist/record/index.js +25 -0
  9. package/dist/record/movements.d.ts +15 -3
  10. package/dist/record/movements.js +42 -7
  11. package/dist/run/index.d.ts +43 -0
  12. package/dist/run/index.js +222 -0
  13. package/dist/run/schemas.d.ts +2016 -0
  14. package/dist/run/schemas.js +223 -0
  15. package/dist/synth/address.d.ts +7 -0
  16. package/dist/synth/address.js +12 -0
  17. package/dist/synth/compose-from-recording.d.ts +4 -3
  18. package/dist/synth/compose-from-recording.js +16 -6
  19. package/dist/synth/deny-cases.d.ts +12 -2
  20. package/dist/synth/deny-cases.js +76 -4
  21. package/dist/synth/evaluate.js +2 -2
  22. package/dist/synth/index.d.ts +1 -0
  23. package/dist/synth/index.js +4 -0
  24. package/dist/synth/minimize.d.ts +1 -1
  25. package/dist/synth/minimize.js +3 -3
  26. package/dist/synth/synthesize-from-recording.d.ts +5 -2
  27. package/dist/synth/synthesize-from-recording.js +175 -69
  28. package/dist/types.d.ts +41 -1
  29. package/dist/types.js +17 -0
  30. package/dist/verify/simulate.js +18 -2
  31. package/dist/verify/verify.js +18 -2
  32. package/dist-cjs/adapters/interpreter/adapter.d.ts +38 -0
  33. package/dist-cjs/adapters/interpreter/adapter.js +527 -0
  34. package/dist-cjs/adapters/interpreter/index.d.ts +1 -0
  35. package/dist-cjs/adapters/interpreter/index.js +8 -0
  36. package/dist-cjs/adapters/oz/adapter.d.ts +20 -0
  37. package/dist-cjs/adapters/oz/adapter.js +289 -0
  38. package/dist-cjs/adapters/oz/index.d.ts +1 -0
  39. package/dist-cjs/adapters/oz/index.js +8 -0
  40. package/dist-cjs/codegen/compile-gate.d.ts +33 -0
  41. package/dist-cjs/codegen/compile-gate.js +123 -0
  42. package/dist-cjs/codegen/index.d.ts +2 -0
  43. package/dist-cjs/codegen/index.js +14 -0
  44. package/dist-cjs/codegen/template.d.ts +18 -0
  45. package/dist-cjs/codegen/template.js +134 -0
  46. package/dist-cjs/errors.d.ts +37 -0
  47. package/dist-cjs/errors.js +3 -0
  48. package/dist-cjs/index.d.ts +11 -0
  49. package/dist-cjs/index.js +27 -0
  50. package/dist-cjs/ir/index.d.ts +1 -0
  51. package/dist-cjs/ir/index.js +3 -0
  52. package/dist-cjs/ir/types.d.ts +108 -0
  53. package/dist-cjs/ir/types.js +12 -0
  54. package/dist-cjs/mandate/index.d.ts +2 -0
  55. package/dist-cjs/mandate/index.js +6 -0
  56. package/dist-cjs/mandate/to-ir.d.ts +3 -0
  57. package/dist-cjs/mandate/to-ir.js +63 -0
  58. package/dist-cjs/mandate/types.d.ts +20 -0
  59. package/dist-cjs/mandate/types.js +9 -0
  60. package/dist-cjs/package.json +3 -0
  61. package/dist-cjs/predicate/encode.d.ts +10 -0
  62. package/dist-cjs/predicate/encode.js +252 -0
  63. package/dist-cjs/predicate/index.d.ts +1 -0
  64. package/dist-cjs/predicate/index.js +6 -0
  65. package/dist-cjs/record/decode.d.ts +79 -0
  66. package/dist-cjs/record/decode.js +441 -0
  67. package/dist-cjs/record/freshness.d.ts +30 -0
  68. package/dist-cjs/record/freshness.js +85 -0
  69. package/dist-cjs/record/index.d.ts +32 -0
  70. package/dist-cjs/record/index.js +191 -0
  71. package/dist-cjs/record/movements.d.ts +32 -0
  72. package/dist-cjs/record/movements.js +227 -0
  73. package/dist-cjs/record/rpc.d.ts +22 -0
  74. package/dist-cjs/record/rpc.js +74 -0
  75. package/dist-cjs/record/validate.d.ts +22 -0
  76. package/dist-cjs/record/validate.js +63 -0
  77. package/dist-cjs/registry/identify.d.ts +11 -0
  78. package/dist-cjs/registry/identify.js +87 -0
  79. package/dist-cjs/registry/index.d.ts +3 -0
  80. package/dist-cjs/registry/index.js +15 -0
  81. package/dist-cjs/registry/known-addresses.d.ts +16 -0
  82. package/dist-cjs/registry/known-addresses.js +53 -0
  83. package/dist-cjs/registry/protocols.d.ts +38 -0
  84. package/dist-cjs/registry/protocols.js +153 -0
  85. package/dist-cjs/review-card/builder.d.ts +14 -0
  86. package/dist-cjs/review-card/builder.js +264 -0
  87. package/dist-cjs/review-card/conflict.d.ts +40 -0
  88. package/dist-cjs/review-card/conflict.js +114 -0
  89. package/dist-cjs/review-card/cross-check.d.ts +11 -0
  90. package/dist-cjs/review-card/cross-check.js +151 -0
  91. package/dist-cjs/review-card/index.d.ts +3 -0
  92. package/dist-cjs/review-card/index.js +10 -0
  93. package/dist-cjs/run/index.d.ts +43 -0
  94. package/dist-cjs/run/index.js +237 -0
  95. package/dist-cjs/run/schemas.d.ts +2016 -0
  96. package/dist-cjs/run/schemas.js +226 -0
  97. package/dist-cjs/seams/index.d.ts +1 -0
  98. package/dist-cjs/seams/index.js +3 -0
  99. package/dist-cjs/seams/types.d.ts +66 -0
  100. package/dist-cjs/seams/types.js +12 -0
  101. package/dist-cjs/synth/address.d.ts +7 -0
  102. package/dist-cjs/synth/address.js +15 -0
  103. package/dist-cjs/synth/compose-from-recording.d.ts +65 -0
  104. package/dist-cjs/synth/compose-from-recording.js +366 -0
  105. package/dist-cjs/synth/deny-cases.d.ts +22 -0
  106. package/dist-cjs/synth/deny-cases.js +438 -0
  107. package/dist-cjs/synth/evaluate.d.ts +39 -0
  108. package/dist-cjs/synth/evaluate.js +428 -0
  109. package/dist-cjs/synth/harness.d.ts +16 -0
  110. package/dist-cjs/synth/harness.js +29 -0
  111. package/dist-cjs/synth/index.d.ts +10 -0
  112. package/dist-cjs/synth/index.js +28 -0
  113. package/dist-cjs/synth/lower.d.ts +23 -0
  114. package/dist-cjs/synth/lower.js +119 -0
  115. package/dist-cjs/synth/minimize.d.ts +4 -0
  116. package/dist-cjs/synth/minimize.js +41 -0
  117. package/dist-cjs/synth/predicate-literals.d.ts +5 -0
  118. package/dist-cjs/synth/predicate-literals.js +28 -0
  119. package/dist-cjs/synth/scope.d.ts +26 -0
  120. package/dist-cjs/synth/scope.js +81 -0
  121. package/dist-cjs/synth/synthesize-from-mandate.d.ts +5 -0
  122. package/dist-cjs/synth/synthesize-from-mandate.js +37 -0
  123. package/dist-cjs/synth/synthesize-from-recording.d.ts +49 -0
  124. package/dist-cjs/synth/synthesize-from-recording.js +759 -0
  125. package/dist-cjs/types.d.ts +311 -0
  126. package/dist-cjs/types.js +55 -0
  127. package/dist-cjs/verify/envelope.d.ts +15 -0
  128. package/dist-cjs/verify/envelope.js +23 -0
  129. package/dist-cjs/verify/index.d.ts +3 -0
  130. package/dist-cjs/verify/index.js +8 -0
  131. package/dist-cjs/verify/simulate.d.ts +31 -0
  132. package/dist-cjs/verify/simulate.js +261 -0
  133. package/dist-cjs/verify/verify.d.ts +21 -0
  134. package/dist-cjs/verify/verify.js +192 -0
  135. package/package.json +43 -3
  136. package/src/codegen/template.ts +3 -3
  137. package/src/contracts/policy-template/OZ_POLICY_TRAIT.md +171 -0
  138. package/src/record/corpus-fixtures.json +532 -0
  139. package/src/record/decode.ts +63 -10
  140. package/src/record/freshness.ts +34 -2
  141. package/src/record/index.ts +40 -0
  142. package/src/record/movements.ts +40 -7
  143. package/src/run/index.ts +277 -0
  144. package/src/run/schemas.ts +250 -0
  145. package/src/synth/address.ts +14 -0
  146. package/src/synth/compose-from-recording.ts +20 -9
  147. package/src/synth/deny-cases.ts +87 -4
  148. package/src/synth/evaluate.ts +2 -2
  149. package/src/synth/index.ts +4 -0
  150. package/src/synth/minimize.ts +7 -3
  151. package/src/synth/synthesize-from-recording.ts +200 -68
  152. package/src/types.ts +37 -1
  153. package/src/verify/simulate.ts +21 -2
  154. package/src/verify/verify.ts +21 -2
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ // packages/policy-synth/src/run/schemas.ts
3
+ //
4
+ // Zod schemas mirroring the policy-synth core domain types. These are the
5
+ // public input / output shapes exposed over MCP and the CLI. They are kept
6
+ // hand-written (rather than derived) because the MCP SDK needs a runtime
7
+ // Zod object at the transport boundary; a drift test asserts they stay in
8
+ // step with the TS source of truth.
9
+ //
10
+ // i128 amounts and other large integers are carried as base-10 decimal strings
11
+ // end-to-end (no JS number coercion). Networks are pinned to the same closed
12
+ // set the core defines. The discriminated union on `source` exposes BOTH
13
+ // synthesize_policy front-ends through a single tool input.
14
+ //
15
+ // This module is the SINGLE source of truth for these shapes. The MCP package
16
+ // imports them here so its tool-shape bindings stay in step; the CLI imports
17
+ // them here so it can build the same args envelope the MCP transport builds.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+ const zod_1 = require("zod");
21
+ const address_ts_1 = require("../synth/address.js");
22
+ /** Soroban `valid_until` is a u32 ledger sequence; a value above this cannot be
23
+ * installed on-chain, so reject it at the boundary (fail-closed). */
24
+ const U32_MAX = 4294967295;
25
+ /** Upper bound on top-level invocations in a recorded transaction. A real
26
+ * Stellar tx caps operations well below this; the bound stops a hand-crafted
27
+ * payload from turning one request into an unbounded synthesis (DoS). */
28
+ const MAX_INVOCATIONS = 512;
29
+ exports.NetworkSchema = zod_1.z.enum(['mainnet', 'testnet']);
30
+ /** ScVal subset - normalised subset the synth consumes. Mirrors
31
+ * `ScVal` in packages/policy-synth/src/types.ts. */
32
+ exports.ScValSchema = zod_1.z.lazy(() => zod_1.z.union([
33
+ zod_1.z.object({ type: zod_1.z.literal('address'), value: zod_1.z.string() }),
34
+ // i128 is SIGNED: real events carry negatives (e.g. a fee-adjustment/refund),
35
+ // so the recorder's own output must round-trip through this schema. u64/u32
36
+ // are unsigned and stay non-negative.
37
+ zod_1.z.object({ type: zod_1.z.literal('i128'), value: zod_1.z.string().regex(/^-?[0-9]+$/) }),
38
+ zod_1.z.object({ type: zod_1.z.literal('u64'), value: zod_1.z.string().regex(/^[0-9]+$/) }),
39
+ zod_1.z.object({ type: zod_1.z.literal('u32'), value: zod_1.z.string().regex(/^[0-9]+$/) }),
40
+ zod_1.z.object({ type: zod_1.z.literal('symbol'), value: zod_1.z.string() }),
41
+ zod_1.z.object({ type: zod_1.z.literal('vec'), value: zod_1.z.array(exports.ScValSchema) }),
42
+ zod_1.z.object({ type: zod_1.z.literal('bytes'), value: zod_1.z.string() }),
43
+ zod_1.z.object({ type: zod_1.z.literal('other'), value: zod_1.z.string() }),
44
+ ]));
45
+ /** ContractInvocation mirrors the core. Annotated with an explicit
46
+ * `z.ZodType<unknown>` (like ScValSchema above) so the self-referential
47
+ * `subInvocations` field does not trip TS's circular type inference. */
48
+ exports.ContractInvocationSchema = zod_1.z.object({
49
+ contract: zod_1.z.string(),
50
+ fn: zod_1.z.string(),
51
+ args: zod_1.z.array(exports.ScValSchema),
52
+ subInvocations: zod_1.z.array(zod_1.z.lazy(() => exports.ContractInvocationSchema)),
53
+ });
54
+ exports.TokenMovementSchema = zod_1.z.object({
55
+ token: zod_1.z.string(),
56
+ from: zod_1.z.string(),
57
+ to: zod_1.z.string(),
58
+ // The recorder reads the amount straight from the signed i128 event value
59
+ // (record/movements.ts readAmount), so a non-standard token that emits a
60
+ // negative transfer/mint/burn amount round-trips as a negative string. Mirror
61
+ // that here; the synth gate, not the wire schema, decides what to do with it.
62
+ amount: zod_1.z.string().regex(/^-?[0-9]+$/),
63
+ });
64
+ exports.OnChainEventSchema = zod_1.z.object({
65
+ contract: zod_1.z.string(),
66
+ topics: zod_1.z.array(zod_1.z.string()),
67
+ data: exports.ScValSchema,
68
+ });
69
+ exports.ParseConfidenceSchema = zod_1.z.object({
70
+ overall: zod_1.z.number().min(0).max(1),
71
+ knownContracts: zod_1.z.array(zod_1.z.string()),
72
+ unknownContracts: zod_1.z.array(zod_1.z.object({
73
+ contract: zod_1.z.string(),
74
+ reason: zod_1.z.enum(['no-abi', 'version-mismatch', 'opaque-result']),
75
+ })),
76
+ opaqueScVals: zod_1.z.array(zod_1.z.object({ path: zod_1.z.string(), type: zod_1.z.string() })),
77
+ thresholdUsed: zod_1.z.number().min(0).max(1),
78
+ });
79
+ /** RecordedTransaction mirrors the core RecordedTransaction. The output shape
80
+ * is referenced by name in the tool result structured content; we deliberately
81
+ * type it loosely (`z.unknown()`) on the success path so the core remains the
82
+ * single source of truth for the wire payload. */
83
+ exports.RecordedTransactionSchema = zod_1.z
84
+ .object({
85
+ network: exports.NetworkSchema,
86
+ signers: zod_1.z.array(zod_1.z.string()),
87
+ invocations: zod_1.z.array(exports.ContractInvocationSchema).max(MAX_INVOCATIONS),
88
+ tokenMovements: zod_1.z.array(exports.TokenMovementSchema),
89
+ events: zod_1.z.array(exports.OnChainEventSchema),
90
+ authEntries: zod_1.z.array(zod_1.z.unknown()),
91
+ ledgerSequence: zod_1.z.number().int().nonnegative(),
92
+ fetchedAt: zod_1.z.number().int().nonnegative(),
93
+ parseConfidence: exports.ParseConfidenceSchema,
94
+ sourceAccount: zod_1.z.string(),
95
+ })
96
+ .passthrough();
97
+ /** MandateSpec mirrors the core MandateSpec. The deterministic Mandate
98
+ * front-end needs no parseConfidence; the tool adapter injects the full
99
+ * confidence after synthesis so the orchestrator can compare. */
100
+ exports.MandateSpecSchema = zod_1.z
101
+ .object({
102
+ chain: zod_1.z.literal('stellar'),
103
+ contract: zod_1.z.string(),
104
+ method: zod_1.z.string().optional(),
105
+ spendingLimit: zod_1.z
106
+ .object({
107
+ token: zod_1.z.string(),
108
+ limit: zod_1.z.string().regex(/^[0-9]+$/),
109
+ windowSeconds: zod_1.z.number().int().positive(),
110
+ })
111
+ .optional(),
112
+ // A threshold of 0 means "0 approvals", which is not a real M-of-N gate.
113
+ approvalThreshold: zod_1.z.number().int().positive().optional(),
114
+ recipients: zod_1.z.array(zod_1.z.string()).optional(),
115
+ expiry: zod_1.z
116
+ .object({
117
+ validUntilLedger: zod_1.z.number().int().positive().max(U32_MAX).optional(),
118
+ validUntilUnixSeconds: zod_1.z.number().int().positive().optional(),
119
+ })
120
+ .optional(),
121
+ })
122
+ .passthrough();
123
+ /** ComposeUserResponses mirrors the core. */
124
+ exports.ComposeUserResponsesSchema = zod_1.z
125
+ .object({
126
+ windowSeconds: zod_1.z.number().int().positive().optional(),
127
+ validUntilLedger: zod_1.z.number().int().positive().max(U32_MAX).optional(),
128
+ limitAmount: zod_1.z
129
+ .string()
130
+ .regex(/^[0-9]+$/)
131
+ .optional(),
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(),
140
+ })
141
+ .passthrough();
142
+ /** OzAdapterConfig - the per-network OZ built-in instance addresses. */
143
+ exports.OzAdapterConfigSchema = zod_1.z.object({
144
+ network: exports.NetworkSchema,
145
+ instances: zod_1.z.object({
146
+ spending_limit: zod_1.z.string(),
147
+ simple_threshold: zod_1.z.string(),
148
+ weighted_threshold: zod_1.z.string(),
149
+ }),
150
+ });
151
+ // ===== record_transaction =====
152
+ exports.RecordTransactionInputSchema = zod_1.z
153
+ .object({
154
+ hash: zod_1.z.string().min(1).optional(),
155
+ xdr: zod_1.z.string().min(1).optional(),
156
+ network: exports.NetworkSchema,
157
+ confidenceOverride: zod_1.z.number().min(0).max(1).optional(),
158
+ })
159
+ .refine((v) => !(v.hash && v.xdr), {
160
+ message: 'provide exactly one of `hash` or `xdr`, not both',
161
+ })
162
+ .refine((v) => Boolean(v.hash) || Boolean(v.xdr), {
163
+ message: 'one of `hash` or `xdr` is required',
164
+ });
165
+ // ===== synthesize_policy =====
166
+ //
167
+ // Discriminated union on `source` exposes BOTH front-ends through ONE tool.
168
+ // - `source: 'mandate'` -> calls synthesizeFromMandate
169
+ // - `source: 'recording'` -> calls synthesizeFromRecording
170
+ exports.SynthesizePolicyMandateInputSchema = zod_1.z.object({
171
+ source: zod_1.z.literal('mandate'),
172
+ mandate: exports.MandateSpecSchema,
173
+ ozConfig: exports.OzAdapterConfigSchema.optional(),
174
+ });
175
+ /** Interpreter opt-in for the recording path. Present -> constraints OZ cannot
176
+ * express (per-method scoping, invocation-count windows, oracle bounds, exact
177
+ * hop paths) lower to a real interpreter predicate document instead of being
178
+ * surfaced as warnings. The core deep-validates `smartAccountAddress` (a C...
179
+ * contract, not the recording's G... source) and the tighten-only oracle
180
+ * bounds; the schema stays light so the core owns the friendly ToolErrors. */
181
+ exports.InterpreterOptionsSchema = zod_1.z.object({
182
+ smartAccountAddress: zod_1.z.string(),
183
+ installNonce: zod_1.z.number().int().positive().optional(),
184
+ oracleParams: zod_1.z
185
+ .object({
186
+ maxStalenessSeconds: zod_1.z.number().int().positive().optional(),
187
+ maxDeviationBps: zod_1.z.number().int().positive().optional(),
188
+ })
189
+ .optional(),
190
+ });
191
+ exports.SynthesizePolicyRecordingInputSchema = zod_1.z.object({
192
+ source: zod_1.z.literal('recording'),
193
+ recordedTx: exports.RecordedTransactionSchema,
194
+ network: exports.NetworkSchema,
195
+ userResponses: exports.ComposeUserResponsesSchema.optional(),
196
+ confidenceOverride: zod_1.z.object({ threshold: zod_1.z.number().min(0).max(1) }).optional(),
197
+ interpreter: exports.InterpreterOptionsSchema.optional(),
198
+ ozConfig: exports.OzAdapterConfigSchema.optional(),
199
+ });
200
+ exports.SynthesizePolicyInputSchema = zod_1.z.discriminatedUnion('source', [
201
+ exports.SynthesizePolicyMandateInputSchema,
202
+ exports.SynthesizePolicyRecordingInputSchema,
203
+ ]);
204
+ // ===== Error envelope (canonical) =====
205
+ //
206
+ // Mirrors ToolError from packages/policy-synth/src/errors.ts. We use a
207
+ // `z.string()` for `code` (not an enum) because the core's ErrorCode union
208
+ // evolves over time; the transport contract only promises a string code the
209
+ // caller can dispatch on. A drift test asserts the canonical codes still
210
+ // pass through unchanged.
211
+ exports.ToolErrorSchema = zod_1.z
212
+ .object({
213
+ code: zod_1.z.string(),
214
+ message: zod_1.z.string(),
215
+ severity: zod_1.z.enum(['info', 'warning', 'error', 'fatal']),
216
+ retryable: zod_1.z.boolean(),
217
+ remediation: zod_1.z
218
+ .object({
219
+ toolCall: zod_1.z.object({ name: zod_1.z.string(), args: zod_1.z.record(zod_1.z.unknown()) }).optional(),
220
+ userQuestion: zod_1.z.object({ code: zod_1.z.string(), question: zod_1.z.string() }).optional(),
221
+ docsUrl: zod_1.z.string().optional(),
222
+ })
223
+ .optional(),
224
+ details: zod_1.z.unknown().optional(),
225
+ })
226
+ .passthrough();
@@ -0,0 +1 @@
1
+ export type { ChainDecoder, CompileResult, CustodyAdapter, CustodyCapabilities, CustodyMode, PolicySource, SimulationResult, } from './types.ts';
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // src/seams/index.ts - re-export the three custody-seam contracts.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,66 @@
1
+ import type { ToolResponse } from '../errors.ts';
2
+ import type { PolicyIR } from '../ir/types.ts';
3
+ import type { Network, ProposedPolicy, RecordedTransaction, ScVal } from '../types.ts';
4
+ /** INPUT: something that produces a normalized RecordedTransaction. */
5
+ export interface PolicySource {
6
+ readonly name: string;
7
+ capture(input: {
8
+ hash?: string;
9
+ xdr?: string;
10
+ network: Network;
11
+ }): Promise<ToolResponse<RecordedTransaction>>;
12
+ }
13
+ /** DECODE: chain-specific call data -> the normalized facts the synth reasons
14
+ * over. */
15
+ export interface ChainDecoder {
16
+ readonly chain: 'stellar' | 'evm';
17
+ decodeInvocation(tx: RecordedTransaction): {
18
+ contract: string;
19
+ method: string;
20
+ args: ScVal[];
21
+ };
22
+ }
23
+ /** OZ = enforce; `log_only` is reserved for shadow mode (out of scope). */
24
+ export type CustodyMode = 'enforce' | 'log_only';
25
+ /** What a backend can express. A construct needing a false flag is flagged
26
+ * `uncovered` by the adapter, never silently dropped. */
27
+ export interface CustodyCapabilities {
28
+ supportsOraclePrice: boolean;
29
+ supportsSpendWindow: boolean;
30
+ supportsInvocationCount: boolean;
31
+ supportsTimeExpiry: boolean;
32
+ supportsThreshold: boolean;
33
+ supportsGeneralPredicate: boolean;
34
+ }
35
+ /** The result of compiling a PolicyIR for one backend. */
36
+ export interface CompileResult {
37
+ /** false => some IR construct this backend cannot express (see `uncovered`). */
38
+ covered: boolean;
39
+ /** Human-readable list of unsupported constructs. */
40
+ uncovered: string[];
41
+ /** The backend-native installable policy, assembled when a rule lowered. */
42
+ proposed?: ProposedPolicy;
43
+ }
44
+ /** Result of a simulate() dry-run. `ts-model` is the off-chain TS evaluator.
45
+ * Real permit/deny semantics wiring is a later phase; week-1 returns a
46
+ * clearly-marked stub (empty `evaluations`, `permitted: null`). */
47
+ export interface SimulationResult {
48
+ backend: 'ts-model';
49
+ /** Whether the permit tx would be allowed; null until real semantics land. */
50
+ permitted: boolean | null;
51
+ /** Per-construct evaluation trace; empty placeholder in this slice. */
52
+ evaluations: unknown[];
53
+ /** Diagnostics; marks the stub explicitly. */
54
+ notes: string[];
55
+ }
56
+ /** OUTPUT: compile a PolicyIR to a backend-specific installable policy. */
57
+ export interface CustodyAdapter {
58
+ readonly name: string;
59
+ readonly mode: CustodyMode;
60
+ capabilities(): CustodyCapabilities;
61
+ compile(ir: PolicyIR): CompileResult;
62
+ /** Phase-03 wiring; a clearly-marked stub in week-1. */
63
+ simulate(ir: PolicyIR, permitTx: RecordedTransaction): SimulationResult;
64
+ /** Canonical JSON of the IR (portability / audit). */
65
+ export(ir: PolicyIR): string;
66
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ // src/seams/types.ts - the three custody seams (contracts, not implementations).
3
+ //
4
+ // The synthesizer is decoupled from its edges by three seams:
5
+ // - PolicySource (INPUT): produces a normalized RecordedTransaction. The
6
+ // recorder is the reference source; a MandateSpec
7
+ // lowering is a second, deterministic source.
8
+ // - ChainDecoder (DECODE): chain-specific call data -> normalized facts.
9
+ // - CustodyAdapter (OUTPUT): compile a PolicyIR to a backend-native policy +
10
+ // verify/export it (OZ is the first adapter).
11
+ // These are INTERFACES only. Implementations live in their own modules.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }
@@ -0,0 +1,65 @@
1
+ import type { IRCompOp, PolicyIR } from '../ir/types.ts';
2
+ import type { AmbiguityPrompt, ContractInvocation, Network } from '../types.ts';
3
+ import type { IntentFacts } from './lower.ts';
4
+ /** Per-asset oracle-price bound supplied by the caller (e.g. swap allowed only
5
+ * if oracle_price(XLM) < 5.00 USDC). One entry per asset; the recorder never
6
+ * fabricates a price bound from a slippage value (different units). */
7
+ export interface OraclePriceBound {
8
+ asset: string;
9
+ operator: IRCompOp;
10
+ value: string;
11
+ }
12
+ /** Caller-supplied answers to the ambiguity prompts. Every numeric bound the
13
+ * synth might apply must come from here - the recording supplies observed
14
+ * amounts (offered only as suggestions), never authorised ceilings. */
15
+ export interface ComposeUserResponses {
16
+ /** Rolling window (seconds) for a spending_limit / invocation_count. */
17
+ windowSeconds?: number;
18
+ /** OZ context-rule expiry (ledger sequence). */
19
+ validUntilLedger?: number;
20
+ /** Per-window spend ceiling (i128 decimal string). Required to emit a
21
+ * spending_limit; absent -> AMOUNT_BOUND_MISSING. */
22
+ limitAmount?: string;
23
+ /** Max invocations per window for an incoming-only flow. Required to emit an
24
+ * invocation_count bound; absent -> FREQUENCY_BOUND_MISSING. */
25
+ invocationLimit?: number;
26
+ /** Per-asset oracle-price bound(s). Each entry lowers to a single
27
+ * `oracle_price(asset) OP value` compare in the interpreter IR. Multiple
28
+ * entries on the same asset emit multiple leaves. */
29
+ oraclePriceBound?: OraclePriceBound[];
30
+ /** Recipient allowlist for a swap (call_arg[3] on SoroSwap's
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. */
35
+ swapRecipientAllowlist?: string[];
36
+ }
37
+ /** Composition options. */
38
+ export interface ComposeOptions {
39
+ network: Network;
40
+ userResponses?: ComposeUserResponses;
41
+ /** When true, constraints the OZ adapter cannot lower are routed to
42
+ * `interpreterIr` (the predicate-shape IR) so the orchestrator can compile
43
+ * them via the interpreter adapter. When false (the default for callers
44
+ * who have not opted in), every constraint goes to `ir` and the OZ
45
+ * adapter's `uncovered` machinery generates the descriptive warnings -
46
+ * today's behaviour. The orchestrator passes this flag through based on
47
+ * whether `opts.interpreter` was supplied. */
48
+ interpreterEnabled?: boolean;
49
+ }
50
+ /** Result of composition: the OZ-shape PolicyIR, the predicate-shape PolicyIR
51
+ * (contains the constraints the OZ adapter cannot lower; empty when
52
+ * `interpreterEnabled` is false), any ambiguities surfaced during inference,
53
+ * and descriptive warnings for needs that are NOT expressed as an IR node
54
+ * (so no fabricated constraint is emitted). The orchestrator carries
55
+ * ambiguities into `ProposedPolicy.ambiguities` and merges warnings into
56
+ * `ProposedPolicy.warnings`. */
57
+ export interface ComposeResult {
58
+ ir: PolicyIR;
59
+ interpreterIr: PolicyIR;
60
+ ambiguities: AmbiguityPrompt[];
61
+ warnings: string[];
62
+ }
63
+ /** Compose a PolicyIR pair from the lowered facts + the resolved scope.
64
+ * Pure (no randomness, no clock); same inputs -> byte-identical result. */
65
+ export declare function composeFromRecording(facts: IntentFacts, scopeContract: string, topLevel: ContractInvocation | null, opts: ComposeOptions): ComposeResult;