@crediolabs/policy-synth 0.1.18 → 0.3.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 (159) hide show
  1. package/README.md +6 -5
  2. package/dist/adapters/interpreter/adapter.d.ts +19 -25
  3. package/dist/adapters/interpreter/adapter.js +54 -487
  4. package/dist/adapters/interpreter/index.d.ts +1 -1
  5. package/dist/adapters/interpreter/index.js +1 -1
  6. package/dist/adapters/oz/adapter.js +1 -15
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/index.d.ts +1 -4
  9. package/dist/index.js +1 -4
  10. package/dist/install/build-add-context-rule.d.ts +3 -11
  11. package/dist/install/build-add-context-rule.js +2 -97
  12. package/dist/install/build-install-policy.d.ts +4 -53
  13. package/dist/install/build-install-policy.js +44 -123
  14. package/dist/ir/index.d.ts +1 -1
  15. package/dist/ir/types.d.ts +7 -76
  16. package/dist/ir/types.js +0 -2
  17. package/dist/predicate/decode.d.ts +1 -1
  18. package/dist/predicate/decode.js +2 -70
  19. package/dist/predicate/encode.js +45 -229
  20. package/dist/predicate/from-json.js +1 -42
  21. package/dist/record/decode.js +3 -8
  22. package/dist/review-card/builder.d.ts +4 -3
  23. package/dist/review-card/builder.js +18 -162
  24. package/dist/review-card/cross-check.js +2 -105
  25. package/dist/review-card/render-leaf.d.ts +4 -0
  26. package/dist/review-card/render-leaf.js +47 -0
  27. package/dist/run/index.d.ts +35 -53
  28. package/dist/run/index.js +115 -156
  29. package/dist/run/schemas.d.ts +168 -1650
  30. package/dist/run/schemas.js +36 -199
  31. package/dist/seams/types.d.ts +0 -2
  32. package/dist/simulate/deny-cases.d.ts +26 -0
  33. package/dist/simulate/deny-cases.js +282 -0
  34. package/dist/simulate/evaluate.d.ts +18 -0
  35. package/dist/simulate/evaluate.js +271 -0
  36. package/dist/simulate/index.d.ts +4 -0
  37. package/dist/simulate/index.js +8 -0
  38. package/dist/synth/compose-from-recording.d.ts +24 -51
  39. package/dist/synth/compose-from-recording.js +120 -282
  40. package/dist/synth/deny-cases.d.ts +5 -7
  41. package/dist/synth/deny-cases.js +23 -373
  42. package/dist/synth/evaluate.d.ts +0 -21
  43. package/dist/synth/evaluate.js +56 -336
  44. package/dist/synth/index.d.ts +0 -5
  45. package/dist/synth/index.js +0 -5
  46. package/dist/synth/permit-context.d.ts +2 -9
  47. package/dist/synth/permit-context.js +1 -82
  48. package/dist/synth/synthesize-from-recording.d.ts +1 -29
  49. package/dist/synth/synthesize-from-recording.js +41 -356
  50. package/dist/types.d.ts +8 -60
  51. package/dist/types.js +5 -2
  52. package/dist/verify/simulate.d.ts +1 -14
  53. package/dist/verify/simulate.js +2 -88
  54. package/dist/verify/verify.d.ts +1 -7
  55. package/dist/verify/verify.js +0 -8
  56. package/dist-cjs/adapters/interpreter/adapter.d.ts +19 -25
  57. package/dist-cjs/adapters/interpreter/adapter.js +56 -489
  58. package/dist-cjs/adapters/interpreter/index.d.ts +1 -1
  59. package/dist-cjs/adapters/interpreter/index.js +1 -2
  60. package/dist-cjs/adapters/oz/adapter.js +1 -15
  61. package/dist-cjs/errors.d.ts +1 -1
  62. package/dist-cjs/index.d.ts +1 -4
  63. package/dist-cjs/index.js +1 -4
  64. package/dist-cjs/install/build-add-context-rule.d.ts +3 -11
  65. package/dist-cjs/install/build-add-context-rule.js +1 -96
  66. package/dist-cjs/install/build-install-policy.d.ts +4 -53
  67. package/dist-cjs/install/build-install-policy.js +44 -122
  68. package/dist-cjs/ir/index.d.ts +1 -1
  69. package/dist-cjs/ir/types.d.ts +7 -76
  70. package/dist-cjs/ir/types.js +0 -2
  71. package/dist-cjs/predicate/decode.d.ts +1 -1
  72. package/dist-cjs/predicate/decode.js +2 -70
  73. package/dist-cjs/predicate/encode.js +45 -229
  74. package/dist-cjs/predicate/from-json.js +1 -42
  75. package/dist-cjs/record/decode.js +3 -8
  76. package/dist-cjs/review-card/builder.d.ts +4 -3
  77. package/dist-cjs/review-card/builder.js +23 -167
  78. package/dist-cjs/review-card/cross-check.js +7 -110
  79. package/dist-cjs/review-card/render-leaf.d.ts +4 -0
  80. package/dist-cjs/review-card/render-leaf.js +52 -0
  81. package/dist-cjs/run/index.d.ts +35 -53
  82. package/dist-cjs/run/index.js +116 -162
  83. package/dist-cjs/run/schemas.d.ts +168 -1650
  84. package/dist-cjs/run/schemas.js +37 -200
  85. package/dist-cjs/seams/types.d.ts +0 -2
  86. package/dist-cjs/simulate/deny-cases.d.ts +26 -0
  87. package/dist-cjs/simulate/deny-cases.js +286 -0
  88. package/dist-cjs/simulate/evaluate.d.ts +18 -0
  89. package/dist-cjs/simulate/evaluate.js +274 -0
  90. package/dist-cjs/simulate/index.d.ts +4 -0
  91. package/dist-cjs/simulate/index.js +13 -0
  92. package/dist-cjs/synth/compose-from-recording.d.ts +24 -51
  93. package/dist-cjs/synth/compose-from-recording.js +120 -282
  94. package/dist-cjs/synth/deny-cases.d.ts +5 -7
  95. package/dist-cjs/synth/deny-cases.js +23 -374
  96. package/dist-cjs/synth/evaluate.d.ts +0 -21
  97. package/dist-cjs/synth/evaluate.js +57 -337
  98. package/dist-cjs/synth/index.d.ts +0 -5
  99. package/dist-cjs/synth/index.js +1 -11
  100. package/dist-cjs/synth/permit-context.d.ts +2 -9
  101. package/dist-cjs/synth/permit-context.js +1 -82
  102. package/dist-cjs/synth/synthesize-from-recording.d.ts +1 -29
  103. package/dist-cjs/synth/synthesize-from-recording.js +39 -354
  104. package/dist-cjs/types.d.ts +8 -60
  105. package/dist-cjs/types.js +6 -3
  106. package/dist-cjs/verify/simulate.d.ts +1 -14
  107. package/dist-cjs/verify/simulate.js +2 -88
  108. package/dist-cjs/verify/verify.d.ts +1 -7
  109. package/dist-cjs/verify/verify.js +0 -8
  110. package/package.json +4 -4
  111. package/src/adapters/interpreter/adapter.ts +76 -572
  112. package/src/errors.ts +0 -19
  113. package/src/index.ts +1 -4
  114. package/src/install/build-add-context-rule.ts +4 -127
  115. package/src/install/build-install-policy.ts +93 -214
  116. package/src/predicate/decode.ts +2 -70
  117. package/src/predicate/encode.ts +49 -261
  118. package/src/predicate/from-json.ts +1 -43
  119. package/src/record/decode.ts +3 -8
  120. package/src/review-card/builder.ts +19 -168
  121. package/src/review-card/cross-check.ts +3 -105
  122. package/src/review-card/render-leaf.ts +48 -0
  123. package/src/run/index.ts +130 -186
  124. package/src/run/schemas.ts +38 -214
  125. package/src/simulate/deny-cases.ts +334 -0
  126. package/src/simulate/evaluate.ts +284 -0
  127. package/src/simulate/index.ts +11 -0
  128. package/src/synth/compose-from-recording.ts +148 -347
  129. package/src/synth/index.ts +0 -13
  130. package/src/synth/synthesize-from-recording.ts +43 -456
  131. package/src/types.ts +13 -49
  132. package/dist/install/build-install-predicate.d.ts +0 -96
  133. package/dist/install/build-install-predicate.js +0 -444
  134. package/dist-cjs/install/build-install-predicate.d.ts +0 -96
  135. package/dist-cjs/install/build-install-predicate.js +0 -479
  136. package/src/adapters/interpreter/index.ts +0 -8
  137. package/src/adapters/oz/adapter.ts +0 -376
  138. package/src/adapters/oz/index.ts +0 -9
  139. package/src/codegen/compile-gate.ts +0 -167
  140. package/src/codegen/index.ts +0 -17
  141. package/src/codegen/template.ts +0 -165
  142. package/src/ir/index.ts +0 -13
  143. package/src/ir/types.ts +0 -132
  144. package/src/mandate/index.ts +0 -4
  145. package/src/mandate/to-ir.ts +0 -71
  146. package/src/mandate/types.ts +0 -21
  147. package/src/seams/index.ts +0 -11
  148. package/src/seams/types.ts +0 -81
  149. package/src/synth/deny-cases.ts +0 -663
  150. package/src/synth/evaluate.ts +0 -613
  151. package/src/synth/harness.ts +0 -68
  152. package/src/synth/minimize.ts +0 -48
  153. package/src/synth/permit-context.ts +0 -136
  154. package/src/synth/predicate-literals.ts +0 -27
  155. package/src/synth/synthesize-from-mandate.ts +0 -82
  156. package/src/verify/envelope.ts +0 -28
  157. package/src/verify/index.ts +0 -5
  158. package/src/verify/simulate.ts +0 -311
  159. package/src/verify/verify.ts +0 -243
@@ -1,165 +0,0 @@
1
- // src/codegen/template.ts - deterministic Rust source generator for the OZ
2
- // `Policy` escape-hatch skeleton.
3
- //
4
- // SUPERSEDED. The architecture this belongs to no longer exists. A deployed
5
- // policy involves exactly two contracts, the OZ smart account and one
6
- // immutable interpreter, and the synthesiser emits policy DATA rather than
7
- // code. Nothing on the synthesis path reaches this module, and no install can
8
- // produce a contract from it. When a constraint falls outside the grammar the
9
- // answer is to version the synthesiser and the interpreter together, not to
10
- // put unaudited Rust on chain per policy.
11
- //
12
- // Deleting it is a decision nobody has taken yet, not a dependency question.
13
- // `compile-gate.ts` takes a Rust string and knows nothing about this module,
14
- // and its tests compile a hardcoded fixture, so the gate keeps working either
15
- // way. What deleting this would remove is the only in-repo producer of policy
16
- // Rust for the gate to check. Do not read its presence as the product
17
- // generating contracts, and do not wire it into the synthesis path.
18
- //
19
- // What follows describes the abandoned design.
20
- //
21
- // This is the LAST-RESORT tool for constraints the v1 DSL cannot express. It
22
- // lives OUT of the audited happy-path surface; the synthesiser never emits it
23
- // automatically. It mirrors Zodiac Roles' `Custom` operator: a hand-written
24
- // (here: scaffolded) policy the operator assumes responsibility for.
25
- //
26
- // The skeleton carries five storage invariants the user MUST preserve when
27
- // filling in the predicate logic:
28
- // 1. `smart_account.require_auth()` is invoked INSIDE `enforce`.
29
- // 2. Storage is keyed by the composite `(smart_account, rule_id)`.
30
- // 3. Chain reads use `unwrap_or_default()` so a missing key is a permitted
31
- // default, not a panic.
32
- // 4. Storage is `persistent()` (NOT `instance()`) so the policy survives
33
- // contract instance lifecycle changes.
34
- // 5. `uninstall` removes the stored state.
35
- //
36
- // Determinism: same `(spec, name)` -> byte-identical output. No clock, no RNG,
37
- // no environment reads.
38
-
39
- /** A small typed description of one uncovered constraint the v1 DSL refused
40
- * to express. The synthesiser surfaces these from `CompileResult.uncovered`
41
- * and the codegen escape hatch wraps them into a hand-auditable skeleton. */
42
- export interface EscapeHatchSpec {
43
- /** Address of the policy contract the skeleton is generated for. */
44
- contract: string
45
- /** The trait method the predicate lives inside (default: 'enforce'). */
46
- fnName?: string
47
- /** Human-readable description of each construct the DSL could not cover. */
48
- uncovered: string[]
49
- }
50
-
51
- /** Format a name for use as a Rust identifier. The input is constrained by
52
- * the caller (a kebab-case policy name + alphanumeric addresses); this is a
53
- * belt-and-braces sanitiser that maps anything non-identifier to '_'. */
54
- function asRustIdent(s: string): string {
55
- let out = ''
56
- for (let i = 0; i < s.length; i++) {
57
- const ch = s.charCodeAt(i)
58
- const ok =
59
- (ch >= 48 && ch <= 57) || // 0-9
60
- (ch >= 65 && ch <= 90) || // A-Z
61
- (ch >= 97 && ch <= 122) || // a-z
62
- ch === 95 // _
63
- out += ok ? s[i] : '_'
64
- }
65
- // Identifiers may not start with a digit.
66
- if (out.length > 0 && out.charCodeAt(0) >= 48 && out.charCodeAt(0) <= 57) {
67
- out = `_${out}`
68
- }
69
- return out || 'policy'
70
- }
71
-
72
- /** Render the `uncovered` list as a `//` comment block (one line per item). */
73
- function renderUncovered(uncovered: string[]): string {
74
- if (uncovered.length === 0) {
75
- return ' // (no specific uncovered constructs reported)'
76
- }
77
- return uncovered.map((line) => ` // - ${line}`).join('\n')
78
- }
79
-
80
- /** Generate a deterministic OZ `Policy` skeleton for an uncovered constraint.
81
- *
82
- * The skeleton is NOT a finished policy; the user fills in the predicate
83
- * logic in the marked TODO(user) holes and audits the result themselves.
84
- *
85
- * Same `(spec, name)` always produces byte-identical output. */
86
- export function generateRust(spec: EscapeHatchSpec, name: string): string {
87
- const _contract = spec.contract
88
- const _fnName = spec.fnName ?? 'enforce'
89
- const structName = asRustIdent(name)
90
- const _dataKeyName = `${asRustIdent(name)}_data`
91
- const uncoveredBlock = renderUncovered(spec.uncovered)
92
-
93
- return `// Auto-generated by the OZ Policy Builder codegen escape hatch.
94
- // THIS OUTPUT IS THE USER'S TO COMPLETE AND AUDIT.
95
- // The synthesiser never emits it on the happy path; reach for it only when
96
- // a constraint cannot be expressed in the v1 DSL grammar (same shape as
97
- // Zodiac Roles' \`Custom\` operator).
98
-
99
- #![no_std]
100
-
101
- use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, Vec};
102
-
103
- #[contracttype]
104
- pub enum DataKey {
105
- /// Composite key \`(smart_account, rule_id)\` so multiple policies on
106
- /// one smart account coexist without collision.
107
- State(Address, u32),
108
- }
109
-
110
- #[contract]
111
- pub struct ${structName};
112
-
113
- #[contractimpl]
114
- impl ${structName} {
115
- pub fn install(e: &Env, smart_account: Address, rule_id: u32) {
116
- // TODO(user): initialise policy state for this (smart_account, rule_id).
117
- // The skeleton leaves the stored value empty; the predicate body
118
- // (in \`enforce\`) decides what to read.
119
- e.storage().persistent().set(
120
- &DataKey::State(smart_account.clone(), rule_id),
121
- &(),
122
- );
123
- }
124
-
125
- pub fn enforce(
126
- e: &Env,
127
- smart_account: Address,
128
- rule_id: u32,
129
- _signers: Vec<Address>,
130
- ) {
131
- // Invariant 1: authorise the smart account BEFORE any state read or
132
- // predicate evaluation. Removing this call opens the policy to
133
- // impersonation by any caller with a valid rule_id.
134
- smart_account.require_auth();
135
-
136
- // Invariant 2 + 4: read stored state through the composite key on
137
- // \`persistent()\` storage. \`unwrap_or_default()\` (invariant 3)
138
- // makes an empty key a permitted default rather than a panic.
139
- let _state: () = e
140
- .storage()
141
- .persistent()
142
- .get(&DataKey::State(smart_account.clone(), rule_id))
143
- .unwrap_or_default();
144
-
145
- // --- uncovered constraints the v1 DSL refused to express ---
146
- ${uncoveredBlock}
147
-
148
- // TODO(user): replace the placeholder below with the real predicate
149
- // body. The recorded flow's \`(contract, fn_name, args)\` is the
150
- // single authorised call \`Policy::enforce\` receives; match against
151
- // \`e\` here.
152
- let _ = (_state, &e);
153
- }
154
-
155
- pub fn uninstall(e: &Env, smart_account: Address, rule_id: u32) {
156
- // Invariant 5: \`uninstall\` removes the stored state so a subsequent
157
- // install starts from a clean slate (and the nonce returns to its
158
- // post-uninstall value).
159
- e.storage()
160
- .persistent()
161
- .remove(&DataKey::State(smart_account, rule_id));
162
- }
163
- }
164
- `
165
- }
package/src/ir/index.ts DELETED
@@ -1,13 +0,0 @@
1
- // src/ir/index.ts - re-export the PolicyIR ("Policy Tree") types.
2
-
3
- export type {
4
- IRCompare,
5
- IRCompOp,
6
- IRCondition,
7
- IRLogic,
8
- IRPolicyRule,
9
- IRScalarType,
10
- IRSelector,
11
- IRVecMode,
12
- PolicyIR,
13
- } from './types.ts'
package/src/ir/types.ts DELETED
@@ -1,132 +0,0 @@
1
- // src/ir/types.ts - the PolicyIR ("Policy Tree").
2
- //
3
- // `PolicyIR` is the custody-agnostic "Policy Tree" hub (the diagram's Policy
4
- // Tree) that every CustodyAdapter compiles FROM. It generalizes the NEAR-V2
5
- // policy schema (roles / scope filter / guard / constraint / comparison leaves /
6
- // default behaviour) into one chain-neutral shape and extends it with the
7
- // OZ-only selectors (spend window, oracle price, invocation count, time) so a
8
- // single IR can serve every backend. NEAR-V2 has no stateful spend window,
9
- // oracle, time/expiry, or invocation-count; those are marked as OZ extensions
10
- // below and are only lowered by adapters that declare support for them.
11
-
12
- /** Comparison operator. Mirrors NEAR-V2 `CompOp`. */
13
- export type IRCompOp = 'eq' | 'lt' | 'lte' | 'gt' | 'gte'
14
-
15
- /** Boolean combinator. Mirrors NEAR-V2 `LogicOp`. */
16
- export type IRLogic = 'and' | 'or'
17
-
18
- /** Vector match mode for a repeated arg. Mirrors NEAR-V2 `VecMode`. */
19
- export type IRVecMode = 'all' | 'any'
20
-
21
- /** Scalar value type. Mirrors NEAR-V2 `ScValType`, superset for OZ. */
22
- export type IRScalarType =
23
- | 'address'
24
- | 'i128'
25
- | 'u128'
26
- | 'u32'
27
- | 'u64'
28
- | 'i64'
29
- | 'symbol'
30
- | 'bytes'
31
- | 'bool'
32
-
33
- /** WHERE in the authorized call a value is read. NEAR-V2 selectors first, OZ
34
- * extensions after (an adapter that cannot express an extension flags it). */
35
- export type IRSelector =
36
- // --- NEAR-V2 selectors ---
37
- | {
38
- kind: 'arg'
39
- argIndex: number
40
- fieldIndex?: number
41
- vecMode?: IRVecMode
42
- scalarType: IRScalarType
43
- } // Stellar StellarComp
44
- | { kind: 'arg_len'; argIndex: number } // length of a vec-typed argument as u32
45
- | {
46
- kind: 'arg_field'
47
- argIndex: number
48
- element: number
49
- field: string
50
- scalarType: IRScalarType
51
- } // field of a map element within a vec-typed argument
52
- | { kind: 'calldata'; offset: number; length: number } // EVM ByteComp
53
- | { kind: 'value' } // EVM tx.value
54
- // --- OZ extensions (not in NEAR-V2; lowered only by adapters that support them) ---
55
- | { kind: 'amount'; token: string } // token amount this call moves
56
- | { kind: 'window_spent'; token: string; windowSeconds: number } // stateful cumulative spend
57
- | { kind: 'invocation_count'; windowSeconds: number }
58
- | { kind: 'now' }
59
- | { kind: 'valid_until' }
60
- | { kind: 'oracle_price'; asset: string }
61
-
62
- /** A single comparison leaf. `value` is a decimal/hex string (i128-safe;
63
- * never a JS number). */
64
- export interface IRCompare {
65
- selector: IRSelector
66
- operator: IRCompOp
67
- value: string
68
- /** Decimal basis of `value`, for selectors whose comparand is not on a basis
69
- * the contract can infer. Set for `oracle_price`, where prices normalise to
70
- * 9 dp and an undeclared basis fails OPEN. */
71
- valueDecimals?: number
72
- }
73
-
74
- /** Condition tree. NEAR-V2 guard/constraint are flat And/Or; the IR allows
75
- * nesting + `not` + `in` + `eq_seq` so the same IR can later lower to the OZ
76
- * predicate DSL. Week-1 adapters only lower the flat supported subset and flag
77
- * the rest. */
78
- export type IRCondition =
79
- | { op: 'and' | 'or'; children: IRCondition[] }
80
- | { op: 'not'; child: IRCondition }
81
- | { op: 'compare'; compare: IRCompare }
82
- | { op: 'in'; selector: IRSelector; values: string[] }
83
- /** Exact ordered sequence equality. The IR-level selector value MUST EQUAL
84
- * `values` as an ORDERED sequence (e.g. a swap hop `path`). `in` is pure
85
- * set membership and cannot express order; `eq_seq` is the construct that
86
- * does. Adapters that cannot express an exact ordered vector (OZ built-ins)
87
- * flag this as `uncovered` rather than silently dropping it. */
88
- | { op: 'eq_seq'; selector: IRSelector; values: string[] }
89
- /** A swap's output floor, expressed against its own input: the output arg
90
- * must be at least `inArgIndex * num / den`.
91
- *
92
- * It is its own construct because `compare` bounds a selector against a
93
- * CONSTANT, and a slippage floor has no constant to bound against - the
94
- * acceptable output depends on the input of the same call. Encoding the
95
- * recorded output as a constant would pin a policy to one trade size.
96
- *
97
- * `num/den` is the caller's minimum acceptable output per unit of input.
98
- * It is never derived from the recording: the recorded rate is a price at
99
- * one moment, and freezing it as policy would deny normal trades later.
100
- * Adapters that cannot express it (the OZ built-ins) flag it `uncovered`. */
101
- | {
102
- op: 'slippage_floor'
103
- outArgIndex: number
104
- inArgIndex: number
105
- num: string
106
- den: string
107
- }
108
-
109
- export interface IRPolicyRule {
110
- /** NEAR-V2 roles whitelist (empty = any; owner exempt). */
111
- roles: string[]
112
- /** NEAR-V2 scope filter; each field optional (absent = wildcard). */
113
- scope: { chainId?: number; contract?: string; method?: string }
114
- /** NEAR-V2 guard (applicability; skip the rule when it fails). */
115
- guard?: IRCondition
116
- /** NEAR-V2 constraint (AND; reject the transaction when any fails). */
117
- constraints: IRCondition[]
118
- // --- OZ extensions ---
119
- /** M-of-N approval (OZ threshold primitives). */
120
- approval?: { kind: 'threshold'; threshold: number; weights?: Record<string, number> }
121
- /** OZ context-rule expiry. */
122
- expiry?: { validUntilLedger?: number; validUntilUnixSeconds?: number }
123
- }
124
-
125
- export interface PolicyIR {
126
- /** NEAR-V2 PolicyChain. */
127
- chain: 'stellar' | 'evm'
128
- /** NEAR-V2 DefaultBehavior. OZ context rules are deny-by-default, so
129
- * `deny_all` is the OZ default fallback when no rule matches. */
130
- defaultBehavior: 'allow_all' | 'deny_all'
131
- rules: IRPolicyRule[]
132
- }
@@ -1,4 +0,0 @@
1
- // src/mandate/index.ts - re-export the Mandate source.
2
-
3
- export { mandateToPolicyIR } from './to-ir.ts'
4
- export type { MandateSpec } from './types.ts'
@@ -1,71 +0,0 @@
1
- // src/mandate/to-ir.ts - deterministic lowering of a MandateSpec to a PolicyIR.
2
- //
3
- // `mandateToPolicyIR` is pure and total: the same spec always lowers to a
4
- // byte-identical PolicyIR. No decoding, no inference, no clock. Each mandate
5
- // field maps to exactly one IR construct:
6
- // contract/method -> rule.scope
7
- // spendingLimit -> a `window_spent(token,window) <= limit` compare
8
- // approvalThreshold-> rule.approval.threshold
9
- // recipients -> an `in` condition on the recipient arg (flagged as not
10
- // covered by the OZ adapter; that is expected)
11
- // expiry -> rule.expiry
12
- // The top-level default is `deny_all` (OZ context rules are deny-by-default).
13
-
14
- import type { IRCondition, IRPolicyRule, IRSelector, PolicyIR } from '../ir/types.ts'
15
- import type { MandateSpec } from './types.ts'
16
-
17
- /** Arg index the recipient allowlist constrains. Pinned to the SEP-41
18
- * `transfer(from, to, amount)` convention where `to` is arg 1. This condition
19
- * is flagged as not covered by the OZ adapter (no built-in primitive expresses
20
- * an arg allowlist), so the index only needs to be deterministic in week-1. */
21
- const RECIPIENT_ARG_INDEX = 1
22
-
23
- export function mandateToPolicyIR(spec: MandateSpec): PolicyIR {
24
- const scope: IRPolicyRule['scope'] = { contract: spec.contract }
25
- if (spec.method !== undefined) scope.method = spec.method
26
-
27
- const constraints: IRCondition[] = []
28
-
29
- if (spec.spendingLimit) {
30
- constraints.push({
31
- op: 'compare',
32
- compare: {
33
- selector: {
34
- kind: 'window_spent',
35
- token: spec.spendingLimit.token,
36
- windowSeconds: spec.spendingLimit.windowSeconds,
37
- },
38
- operator: 'lte',
39
- value: spec.spendingLimit.limit,
40
- },
41
- })
42
- }
43
-
44
- if (spec.recipients && spec.recipients.length > 0) {
45
- const selector: IRSelector = {
46
- kind: 'arg',
47
- argIndex: RECIPIENT_ARG_INDEX,
48
- scalarType: 'address',
49
- }
50
- constraints.push({ op: 'in', selector, values: [...spec.recipients] })
51
- }
52
-
53
- const rule: IRPolicyRule = { roles: [], scope, constraints }
54
-
55
- if (spec.approvalThreshold !== undefined) {
56
- rule.approval = { kind: 'threshold', threshold: spec.approvalThreshold }
57
- }
58
-
59
- if (spec.expiry) {
60
- const expiry: NonNullable<IRPolicyRule['expiry']> = {}
61
- if (spec.expiry.validUntilLedger !== undefined) {
62
- expiry.validUntilLedger = spec.expiry.validUntilLedger
63
- }
64
- if (spec.expiry.validUntilUnixSeconds !== undefined) {
65
- expiry.validUntilUnixSeconds = spec.expiry.validUntilUnixSeconds
66
- }
67
- rule.expiry = expiry
68
- }
69
-
70
- return { chain: spec.chain, defaultBehavior: 'deny_all', rules: [rule] }
71
- }
@@ -1,21 +0,0 @@
1
- // src/mandate/types.ts - the deterministic Mandate source.
2
- //
3
- // A MandateSpec is a declarative policy statement: "this account may call this
4
- // contract, spending at most L per window, requiring M approvals, to these
5
- // recipients, until this expiry". It carries NO transaction to decode, NO
6
- // parseConfidence, and NO inference - it lowers deterministically to a
7
- // PolicyIR. It is the clean end-to-end demo path, co-equal with the recorder.
8
-
9
- export interface MandateSpec {
10
- chain: 'stellar'
11
- contract: string
12
- method?: string
13
- /** -> OZ `spending_limit` primitive. `limit` is an i128 decimal string. */
14
- spendingLimit?: { token: string; limit: string; windowSeconds: number }
15
- /** -> OZ `simple_threshold` / `weighted_threshold` primitive. */
16
- approvalThreshold?: number
17
- /** Recipient allowlist -> an `in` arg condition (not covered by an OZ built-in). */
18
- recipients?: string[]
19
- /** -> OZ context-rule expiry. */
20
- expiry?: { validUntilLedger?: number; validUntilUnixSeconds?: number }
21
- }
@@ -1,11 +0,0 @@
1
- // src/seams/index.ts - re-export the three custody-seam contracts.
2
-
3
- export type {
4
- ChainDecoder,
5
- CompileResult,
6
- CustodyAdapter,
7
- CustodyCapabilities,
8
- CustodyMode,
9
- PolicySource,
10
- SimulationResult,
11
- } from './types.ts'
@@ -1,81 +0,0 @@
1
- // src/seams/types.ts - the three custody seams (contracts, not implementations).
2
- //
3
- // The synthesizer is decoupled from its edges by three seams:
4
- // - PolicySource (INPUT): produces a normalized RecordedTransaction. The
5
- // recorder is the reference source; a MandateSpec
6
- // lowering is a second, deterministic source.
7
- // - ChainDecoder (DECODE): chain-specific call data -> normalized facts.
8
- // - CustodyAdapter (OUTPUT): compile a PolicyIR to a backend-native policy +
9
- // verify/export it (OZ is the first adapter).
10
- // These are INTERFACES only. Implementations live in their own modules.
11
-
12
- import type { ToolResponse } from '../errors.ts'
13
- import type { PolicyIR } from '../ir/types.ts'
14
- import type { Network, ProposedPolicy, RecordedTransaction, ScVal } from '../types.ts'
15
-
16
- /** INPUT: something that produces a normalized RecordedTransaction. */
17
- export interface PolicySource {
18
- readonly name: string
19
- capture(input: {
20
- hash?: string
21
- xdr?: string
22
- network: Network
23
- }): Promise<ToolResponse<RecordedTransaction>>
24
- }
25
-
26
- /** DECODE: chain-specific call data -> the normalized facts the synth reasons
27
- * over. */
28
- export interface ChainDecoder {
29
- readonly chain: 'stellar' | 'evm'
30
- decodeInvocation(tx: RecordedTransaction): { contract: string; method: string; args: ScVal[] }
31
- }
32
-
33
- /** OZ = enforce; `log_only` is reserved for shadow mode (out of scope). */
34
- export type CustodyMode = 'enforce' | 'log_only'
35
-
36
- /** What a backend can express. A construct needing a false flag is flagged
37
- * `uncovered` by the adapter, never silently dropped. */
38
- export interface CustodyCapabilities {
39
- supportsOraclePrice: boolean
40
- supportsSpendWindow: boolean
41
- supportsInvocationCount: boolean
42
- supportsTimeExpiry: boolean
43
- supportsThreshold: boolean
44
- supportsGeneralPredicate: boolean
45
- }
46
-
47
- /** The result of compiling a PolicyIR for one backend. */
48
- export interface CompileResult {
49
- /** false => some IR construct this backend cannot express (see `uncovered`). */
50
- covered: boolean
51
- /** Human-readable list of unsupported constructs. */
52
- uncovered: string[]
53
- /** The backend-native installable policy, assembled when a rule lowered. */
54
- proposed?: ProposedPolicy
55
- }
56
-
57
- /** Result of a simulate() dry-run. `ts-model` is the off-chain TS evaluator.
58
- * Real permit/deny semantics wiring is a later phase; week-1 returns a
59
- * clearly-marked stub (empty `evaluations`, `permitted: null`). */
60
- export interface SimulationResult {
61
- backend: 'ts-model'
62
- /** Whether the permit tx would be allowed; null until real semantics land. */
63
- permitted: boolean | null
64
- /** Per-construct evaluation trace; empty placeholder in this slice. */
65
- evaluations: unknown[]
66
- /** Diagnostics; marks the stub explicitly. */
67
- notes: string[]
68
- }
69
-
70
- /** OUTPUT: compile a PolicyIR to a backend-specific installable policy. */
71
- export interface CustodyAdapter {
72
- readonly name: string
73
- readonly mode: CustodyMode
74
- capabilities(): CustodyCapabilities
75
- compile(ir: PolicyIR): CompileResult
76
- /** Phase-03 wiring; a clearly-marked stub in week-1. */
77
- simulate(ir: PolicyIR, permitTx: RecordedTransaction): SimulationResult
78
- /** Canonical JSON of the IR (portability / audit). */
79
- export(ir: PolicyIR): string
80
- // install(...) lands with a later phase; not part of this slice.
81
- }