@crediolabs/policy-synth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/adapters/oz/adapter.d.ts +20 -0
  2. package/dist/adapters/oz/adapter.js +282 -0
  3. package/dist/adapters/oz/index.d.ts +1 -0
  4. package/dist/adapters/oz/index.js +2 -0
  5. package/dist/errors.d.ts +37 -0
  6. package/dist/errors.js +2 -0
  7. package/dist/index.d.ts +9 -0
  8. package/dist/index.js +9 -0
  9. package/dist/ir/index.d.ts +1 -0
  10. package/dist/ir/index.js +2 -0
  11. package/dist/ir/types.d.ts +97 -0
  12. package/dist/ir/types.js +11 -0
  13. package/dist/mandate/index.d.ts +2 -0
  14. package/dist/mandate/index.js +2 -0
  15. package/dist/mandate/to-ir.d.ts +3 -0
  16. package/dist/mandate/to-ir.js +60 -0
  17. package/dist/mandate/types.d.ts +20 -0
  18. package/dist/mandate/types.js +8 -0
  19. package/dist/record/decode.d.ts +76 -0
  20. package/dist/record/decode.js +372 -0
  21. package/dist/record/freshness.d.ts +17 -0
  22. package/dist/record/freshness.js +50 -0
  23. package/dist/record/index.d.ts +21 -0
  24. package/dist/record/index.js +163 -0
  25. package/dist/record/movements.d.ts +20 -0
  26. package/dist/record/movements.js +187 -0
  27. package/dist/record/rpc.d.ts +22 -0
  28. package/dist/record/rpc.js +70 -0
  29. package/dist/record/validate.d.ts +22 -0
  30. package/dist/record/validate.js +60 -0
  31. package/dist/registry/identify.d.ts +11 -0
  32. package/dist/registry/identify.js +84 -0
  33. package/dist/registry/index.d.ts +3 -0
  34. package/dist/registry/index.js +4 -0
  35. package/dist/registry/known-addresses.d.ts +16 -0
  36. package/dist/registry/known-addresses.js +49 -0
  37. package/dist/registry/protocols.d.ts +38 -0
  38. package/dist/registry/protocols.js +149 -0
  39. package/dist/seams/index.d.ts +1 -0
  40. package/dist/seams/index.js +2 -0
  41. package/dist/seams/types.d.ts +66 -0
  42. package/dist/seams/types.js +11 -0
  43. package/dist/synth/compose-from-recording.d.ts +36 -0
  44. package/dist/synth/compose-from-recording.js +162 -0
  45. package/dist/synth/index.d.ts +5 -0
  46. package/dist/synth/index.js +6 -0
  47. package/dist/synth/lower.d.ts +23 -0
  48. package/dist/synth/lower.js +116 -0
  49. package/dist/synth/scope.d.ts +26 -0
  50. package/dist/synth/scope.js +77 -0
  51. package/dist/synth/synthesize-from-mandate.d.ts +5 -0
  52. package/dist/synth/synthesize-from-mandate.js +34 -0
  53. package/dist/synth/synthesize-from-recording.d.ts +17 -0
  54. package/dist/synth/synthesize-from-recording.js +178 -0
  55. package/dist/types.d.ts +249 -0
  56. package/dist/types.js +35 -0
  57. package/package.json +37 -0
  58. package/src/adapters/oz/adapter.ts +363 -0
  59. package/src/adapters/oz/index.ts +9 -0
  60. package/src/errors.ts +79 -0
  61. package/src/index.ts +9 -0
  62. package/src/ir/index.ts +13 -0
  63. package/src/ir/types.ts +94 -0
  64. package/src/mandate/index.ts +4 -0
  65. package/src/mandate/to-ir.ts +71 -0
  66. package/src/mandate/types.ts +21 -0
  67. package/src/record/decode.ts +500 -0
  68. package/src/record/freshness.ts +63 -0
  69. package/src/record/index.ts +224 -0
  70. package/src/record/movements.ts +188 -0
  71. package/src/record/rpc.ts +88 -0
  72. package/src/record/validate.ts +75 -0
  73. package/src/registry/identify.ts +99 -0
  74. package/src/registry/index.ts +24 -0
  75. package/src/registry/known-addresses.ts +71 -0
  76. package/src/registry/protocols.ts +176 -0
  77. package/src/seams/index.ts +11 -0
  78. package/src/seams/types.ts +81 -0
  79. package/src/synth/compose-from-recording.ts +226 -0
  80. package/src/synth/index.ts +19 -0
  81. package/src/synth/lower.ts +144 -0
  82. package/src/synth/scope.ts +108 -0
  83. package/src/synth/synthesize-from-mandate.ts +46 -0
  84. package/src/synth/synthesize-from-recording.ts +226 -0
  85. package/src/types.ts +218 -0
package/src/types.ts ADDED
@@ -0,0 +1,218 @@
1
+ // src/types.ts - shared domain types (single source of truth)
2
+
3
+ export type Network = 'mainnet' | 'testnet'
4
+
5
+ /** Decoded Soroban value - the normalised subset synthesis needs. */
6
+ export type ScVal =
7
+ | { type: 'address'; value: string }
8
+ | { type: 'i128'; value: string }
9
+ | { type: 'u64'; value: string }
10
+ | { type: 'u32'; value: string }
11
+ | { type: 'symbol'; value: string }
12
+ | { type: 'vec'; value: ScVal[] }
13
+ | { type: 'bytes'; value: string }
14
+ | { type: 'other'; value: string }
15
+
16
+ /** OZ Accounts framework hard limits (verified against canonical OZ source). */
17
+ export const OZ_LIMITS = {
18
+ maxPoliciesPerRule: 5,
19
+ maxSignersPerRule: 15,
20
+ // NO per-account rule cap - OZ imposes none.
21
+ } as const
22
+
23
+ /** Soroban / Stellar protocol limits used to bound inputs fail-closed. Single
24
+ * source so the schema boundary and the core validators cannot drift. */
25
+ export const SOROBAN_LIMITS = {
26
+ /** `valid_until` and other ledger-sequence fields are u32. */
27
+ u32Max: 4294967295,
28
+ /** Upper bound on top-level invocations in a recorded transaction; a real
29
+ * Stellar tx caps operations well below this. Bounds a hand-crafted payload. */
30
+ maxInvocations: 512,
31
+ /** Stellar targets a ~5s ledger close time (DAY_IN_LEDGERS = 17280 = 86400/5),
32
+ * used to convert a spend window in seconds to OZ `period_ledgers`. */
33
+ secondsPerLedger: 5,
34
+ } as const
35
+
36
+ /** Predicate-document caps - enforced fail-closed at install by BOTH the synth (TS)
37
+ * AND the interpreter (Rust). Single source: a CI test asserts the two cap sets are
38
+ * byte-identical (TS reads the same JSON manifest the Rust build emits). If they
39
+ * disagree, the interpreter is authoritative. Rationale per cap:
40
+ * - MAX_DEPTH = 5: 3 walkthroughs need depth 2; budget = 2x growth + 1 for `not` chain.
41
+ * - MAX_LEAVES = 200: allowlists (recipients, paths) typically < 100; budget = 2x + 1 nested allowlist.
42
+ * - MAX_PREDICATE_BYTES = 32 KB: well under the per-ledger-entry 64 KB persistent-storage limit.
43
+ * - MAX_ORACLE_READS = 6: two-round confirmation per asset consumes 2 reads; cap accommodates a predicate that references up to 3 unique oracle assets (3 * 2 = 6 reads). Stays a fail-closed install-time cap.
44
+ * - MAX_IN_OPERAND_COUNT = 32: allowlist < 32 is a UX-natural bound. */
45
+ export const PREDICATE_CAPS = {
46
+ MAX_DEPTH: 5,
47
+ MAX_LEAVES: 200,
48
+ MAX_PREDICATE_BYTES: 32 * 1024,
49
+ MAX_ORACLE_READS: 6,
50
+ MAX_IN_OPERAND_COUNT: 32,
51
+ } as const
52
+
53
+ /** OZ context-rule draft - the unit the install builder builds add_context_rule from. */
54
+ export type ContextRuleDraft = {
55
+ contextRuleType:
56
+ | { kind: 'default' }
57
+ | { kind: 'call_contract'; contract: string }
58
+ | { kind: 'create_contract'; wasmHash: string }
59
+ name: string
60
+ /** Ledger sequence (u32). Soroban `valid_until: Option<u32>`. */
61
+ validUntilLedger: number | null
62
+ /** Mirrors OZ `Signer` enum exactly: `Delegated(Address) | External(Address, Bytes)`.
63
+ * The verifier address + key bytes for an external signer are carried in the
64
+ * `External` variant - there is no `Key` or `SmartAccount` variant, and no
65
+ * master/non-master discriminator lives on the type itself. */
66
+ signers: SignerDraft[]
67
+ policies: PolicyRef[]
68
+ }
69
+
70
+ /** Mirrors OZ `Signer` exactly: `Signer::Delegated(Address) | Signer::External(Address, Bytes)`. */
71
+ export type SignerDraft =
72
+ | { kind: 'delegated'; address: string }
73
+ | { kind: 'external'; verifier: string; keyBytes: string }
74
+
75
+ /** Reference to one policy attached to a context rule. */
76
+ export type PolicyRef =
77
+ | { kind: 'oz_builtin'; primitive: OZPrimitiveConfig; instanceAddress: string }
78
+ | { kind: 'interpreter'; interpreterAddress: string; predicateBlobBase64: string }
79
+
80
+ /** A serialised predicate document stored against a (smart_account, rule_id) pair. */
81
+ export interface PolicyDocument {
82
+ /** Grammar version baked into the interpreter wasm. Fail-closed strict match at install
83
+ * AND at evaluate (defence in depth). NOT the per-edit revision. */
84
+ grammarVersion: 1
85
+ /** Per-rule install nonce. Must equal the interpreter's stored nonce + 1 at install.
86
+ * First install accepts nonce = 1 with stored nonce = 0. Re-install is structurally
87
+ * possible by incrementing the nonce; uninstall removes the nonce with state so a
88
+ * subsequent install returns to nonce = 1. Replay of an old install denied. */
89
+ installNonce: number
90
+ /** Canonical ScVal encoding (NOT JSON) - the version-match bypass is closed by the
91
+ * on-chain serialisation, not by a parser. */
92
+ encodedPredicate: string // base64 of canonical ScVal
93
+ /** Hash of the post-canonicalisation bytes of encodedPredicate. Stored alongside the
94
+ * doc and re-verified at evaluate. */
95
+ predicateHash: string
96
+ /** Per-policy overrides of oracle thresholds; absent = use defaults.
97
+ * Overrides may TIGHTEN only (never widen) - the interpreter rejects any per-policy
98
+ * value looser than the wasm-level defaults at install. */
99
+ oracleParams?: {
100
+ /** Seconds; must be <= MAX_ORACLE_STALENESS_SECONDS (600). */
101
+ maxStalenessSeconds?: number
102
+ /** Basis points; must be <= MAX_ORACLE_DEVIATION_BPS (200). */
103
+ maxDeviationBps?: number
104
+ }
105
+ }
106
+
107
+ /** The versioned predicate AST (interpreter side). Tagged union. */
108
+ export type PredicateNode =
109
+ | { op: 'and'; children: PredicateNode[] }
110
+ | { op: 'or'; children: PredicateNode[] }
111
+ | { op: 'not'; child: PredicateNode }
112
+ | { op: 'eq'; left: PredicateLeaf; right: PredicateLeaf }
113
+ | { op: 'lt' | 'lte' | 'gt' | 'gte'; left: PredicateLeaf; right: PredicateLeaf }
114
+ | { op: 'in'; needle: PredicateLeaf; haystack: PredicateLeaf[] }
115
+
116
+ export type PredicateLeaf =
117
+ | { kind: 'call_contract' }
118
+ | { kind: 'call_fn' }
119
+ | { kind: 'call_arg'; index: number }
120
+ // `call_sub_invocation[i]` is NOT in v1 grammar. OZ `Policy::enforce`
121
+ // receives one `Context`, not a sub-invocation tree; sub-invocations live under
122
+ // `InvokerContractAuthEntry::Contract(SubContractInvocation)`, not in the
123
+ // enforce Context. Scope is per-authorized-context; a v1.1 sub-invocation
124
+ // tree leaf is gated on the enforce-Context-shape spike result.
125
+ | { kind: 'amount'; token: string }
126
+ | { kind: 'window_spent'; token: string }
127
+ | { kind: 'now' }
128
+ | { kind: 'valid_until' }
129
+ | { kind: 'invocation_count_in_window'; windowSecs: number }
130
+ | { kind: 'oracle_price'; asset: string } // Stellar Address (SAC address for SEP-41; XLM uses its network SAC)
131
+
132
+ export interface OZPrimitiveConfig {
133
+ primitive: 'spending_limit' | 'simple_threshold' | 'weighted_threshold'
134
+ params: Record<string, unknown>
135
+ }
136
+
137
+ export interface ContractInvocation {
138
+ contract: string
139
+ fn: string
140
+ args: ScVal[]
141
+ subInvocations: ContractInvocation[]
142
+ }
143
+
144
+ export interface TokenMovement {
145
+ token: string
146
+ from: string
147
+ to: string
148
+ amount: string
149
+ }
150
+
151
+ export interface OnChainEvent {
152
+ contract: string
153
+ topics: string[]
154
+ data: ScVal
155
+ }
156
+
157
+ export interface RecordedTransaction {
158
+ network: Network
159
+ /** Authorising signers (the agent / owner set). */
160
+ signers: string[]
161
+ /** Top-level call + nested sub-invocations captured for analysis:
162
+ * v1 grammar matches only the top-level call's own `(contract, fn_name, args)`
163
+ * that `Policy::enforce` receives - sub-invocations are NOT walked in v1. */
164
+ invocations: ContractInvocation[]
165
+ /** All token movements (from events). */
166
+ tokenMovements: TokenMovement[]
167
+ /** Raw on-chain events from getTransaction (used for validation). */
168
+ events: OnChainEvent[]
169
+ /** Soroban auth entries (which signers, which contracts, which fns). */
170
+ authEntries: unknown[]
171
+ ledgerSequence: number
172
+ fetchedAt: number
173
+ /** The parsed-decoding freshness, computed by the recorder. The synth refuses on
174
+ * parseConfidence.overall < parseConfidence.thresholdUsed. */
175
+ parseConfidence: ParseConfidence
176
+ /** Source account (already recorded, needed by install + reviewer). */
177
+ sourceAccount: string
178
+ }
179
+
180
+ export interface ProposedPolicy {
181
+ contextRule: ContextRuleDraft
182
+ policyDocuments: PolicyDocument[]
183
+ policyRefs: PolicyRef[]
184
+ /** Parsed-decoding freshness mirror; the orchestrator refuses when below threshold. */
185
+ parseConfidence: ParseConfidence
186
+ warnings: string[]
187
+ ambiguities: AmbiguityPrompt[]
188
+ }
189
+
190
+ export interface AmbiguityPrompt {
191
+ code: AmbiguityCode
192
+ question: string
193
+ }
194
+
195
+ /** Catalogue of named ambiguity codes (the skill's prompt files are one-per-code).
196
+ * Adding a code is a synthesizer decision; the prompt authoring must not
197
+ * invent codes outside this union. */
198
+ export type AmbiguityCode =
199
+ | 'DURATION_UNSPECIFIED'
200
+ | 'AMOUNT_BOUND_MISSING'
201
+ | 'RECIPIENT_ALLOWLIST_EMPTY'
202
+ | 'FREQUENCY_BOUND_MISSING'
203
+ | 'ORACLE_ASSET_UNKNOWN'
204
+ | 'MULTIPLE_UNRELATED_TARGETS'
205
+
206
+ /** Structured recording freshness. `overall` is the gate threshold (default 1.0); the
207
+ * breakdown is the diagnostic the user / agent sees when the gate fires. The
208
+ * computation rule is pinned: `overall = 1 - (unknownContracts + opaqueScVals) / total`. */
209
+ export interface ParseConfidence {
210
+ overall: number // 0..1
211
+ knownContracts: string[]
212
+ unknownContracts: Array<{
213
+ contract: string
214
+ reason: 'no-abi' | 'version-mismatch' | 'opaque-result'
215
+ }>
216
+ opaqueScVals: Array<{ path: string; type: string }>
217
+ thresholdUsed: number // 1.0 by default; > 0 only via explicit confidence_override
218
+ }