@wlfi-agent/cli 1.4.13 → 1.4.15

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 (289) hide show
  1. package/Cargo.lock +3968 -0
  2. package/Cargo.toml +50 -0
  3. package/README.md +426 -6
  4. package/crates/vault-cli-admin/Cargo.toml +26 -0
  5. package/crates/vault-cli-admin/src/io_utils.rs +500 -0
  6. package/crates/vault-cli-admin/src/main.rs +3990 -0
  7. package/crates/vault-cli-admin/src/shared_config.rs +624 -0
  8. package/crates/vault-cli-admin/src/tui/amounts.rs +180 -0
  9. package/crates/vault-cli-admin/src/tui/token_rpc.rs +250 -0
  10. package/crates/vault-cli-admin/src/tui/utils.rs +82 -0
  11. package/crates/vault-cli-admin/src/tui.rs +3410 -0
  12. package/crates/vault-cli-agent/Cargo.toml +24 -0
  13. package/crates/vault-cli-agent/src/io_utils.rs +576 -0
  14. package/crates/vault-cli-agent/src/main.rs +833 -0
  15. package/crates/vault-cli-daemon/Cargo.toml +28 -0
  16. package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +216 -0
  17. package/crates/vault-cli-daemon/src/main.rs +644 -0
  18. package/crates/vault-cli-daemon/src/relay_sync.rs +894 -0
  19. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +167 -0
  20. package/crates/vault-daemon/Cargo.toml +32 -0
  21. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +1041 -0
  22. package/crates/vault-daemon/src/daemon_parts/core_helpers.rs +1256 -0
  23. package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +622 -0
  24. package/crates/vault-daemon/src/lib.rs +54 -0
  25. package/crates/vault-daemon/src/persistence.rs +441 -0
  26. package/crates/vault-daemon/src/tests.rs +237 -0
  27. package/crates/vault-daemon/src/tests_parts/part1.rs +1224 -0
  28. package/crates/vault-daemon/src/tests_parts/part2.rs +1021 -0
  29. package/crates/vault-daemon/src/tests_parts/part3.rs +835 -0
  30. package/crates/vault-daemon/src/tests_parts/part4.rs +604 -0
  31. package/crates/vault-domain/Cargo.toml +20 -0
  32. package/crates/vault-domain/src/action.rs +849 -0
  33. package/crates/vault-domain/src/address.rs +51 -0
  34. package/crates/vault-domain/src/approval.rs +90 -0
  35. package/crates/vault-domain/src/constants.rs +4 -0
  36. package/crates/vault-domain/src/error.rs +54 -0
  37. package/crates/vault-domain/src/keys.rs +71 -0
  38. package/crates/vault-domain/src/lib.rs +42 -0
  39. package/crates/vault-domain/src/nonce.rs +102 -0
  40. package/crates/vault-domain/src/policy.rs +172 -0
  41. package/crates/vault-domain/src/request.rs +53 -0
  42. package/crates/vault-domain/src/scope.rs +24 -0
  43. package/crates/vault-domain/src/session.rs +50 -0
  44. package/crates/vault-domain/src/signature.rs +34 -0
  45. package/crates/vault-domain/src/tests.rs +651 -0
  46. package/crates/vault-domain/src/u128_as_decimal_string.rs +44 -0
  47. package/crates/vault-policy/Cargo.toml +17 -0
  48. package/crates/vault-policy/src/engine.rs +301 -0
  49. package/crates/vault-policy/src/error.rs +81 -0
  50. package/crates/vault-policy/src/lib.rs +17 -0
  51. package/crates/vault-policy/src/report.rs +34 -0
  52. package/crates/vault-policy/src/tests.rs +891 -0
  53. package/crates/vault-policy/src/tests_explain.rs +78 -0
  54. package/crates/vault-sdk-agent/Cargo.toml +21 -0
  55. package/crates/vault-sdk-agent/src/lib.rs +711 -0
  56. package/crates/vault-signer/Cargo.toml +25 -0
  57. package/crates/vault-signer/src/lib.rs +731 -0
  58. package/crates/vault-signer/tests/secure_enclave_acl.rs +54 -0
  59. package/crates/vault-transport-unix/Cargo.toml +24 -0
  60. package/crates/vault-transport-unix/src/lib.rs +1640 -0
  61. package/crates/vault-transport-xpc/Cargo.toml +25 -0
  62. package/crates/vault-transport-xpc/src/client_codec_api.rs +635 -0
  63. package/crates/vault-transport-xpc/src/lib.rs +680 -0
  64. package/crates/vault-transport-xpc/src/tests.rs +818 -0
  65. package/crates/vault-transport-xpc/tests/e2e_flow.rs +773 -0
  66. package/dist/cli.cjs +35088 -0
  67. package/dist/cli.cjs.map +1 -0
  68. package/package.json +45 -41
  69. package/packages/cache/.turbo/turbo-build.log +52 -0
  70. package/packages/cache/dist/chunk-2QFWMUXT.cjs +43 -0
  71. package/packages/cache/dist/chunk-2QFWMUXT.cjs.map +1 -0
  72. package/packages/cache/dist/chunk-4U63TZTQ.js +43 -0
  73. package/packages/cache/dist/chunk-4U63TZTQ.js.map +1 -0
  74. package/packages/cache/dist/chunk-ALQ6H7KG.cjs +404 -0
  75. package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +1 -0
  76. package/packages/cache/dist/chunk-FGJEEF5N.js +404 -0
  77. package/packages/cache/dist/chunk-FGJEEF5N.js.map +1 -0
  78. package/packages/cache/dist/chunk-UYNEHZHB.cjs +45 -0
  79. package/packages/cache/dist/chunk-UYNEHZHB.cjs.map +1 -0
  80. package/packages/cache/dist/chunk-VXVMPG3W.js +45 -0
  81. package/packages/cache/dist/chunk-VXVMPG3W.js.map +1 -0
  82. package/packages/cache/dist/client/index.cjs +11 -0
  83. package/packages/cache/dist/client/index.cjs.map +1 -0
  84. package/packages/cache/dist/client/index.d.cts +15 -0
  85. package/packages/cache/dist/client/index.d.ts +15 -0
  86. package/packages/cache/dist/client/index.js +11 -0
  87. package/packages/cache/dist/client/index.js.map +1 -0
  88. package/packages/cache/dist/errors/index.cjs +11 -0
  89. package/packages/cache/dist/errors/index.cjs.map +1 -0
  90. package/packages/cache/dist/errors/index.d.cts +26 -0
  91. package/packages/cache/dist/errors/index.d.ts +26 -0
  92. package/packages/cache/dist/errors/index.js +11 -0
  93. package/packages/cache/dist/errors/index.js.map +1 -0
  94. package/packages/cache/dist/index.cjs +29 -0
  95. package/packages/cache/dist/index.cjs.map +1 -0
  96. package/packages/cache/dist/index.d.cts +4 -0
  97. package/packages/cache/dist/index.d.ts +4 -0
  98. package/packages/cache/dist/index.js +29 -0
  99. package/packages/cache/dist/index.js.map +1 -0
  100. package/packages/cache/dist/service/index.cjs +15 -0
  101. package/packages/cache/dist/service/index.cjs.map +1 -0
  102. package/packages/cache/dist/service/index.d.cts +184 -0
  103. package/packages/cache/dist/service/index.d.ts +184 -0
  104. package/packages/cache/dist/service/index.js +15 -0
  105. package/packages/cache/dist/service/index.js.map +1 -0
  106. package/packages/cache/node_modules/.bin/jiti +17 -0
  107. package/packages/cache/node_modules/.bin/tsc +17 -0
  108. package/packages/cache/node_modules/.bin/tsserver +17 -0
  109. package/packages/cache/node_modules/.bin/tsup +17 -0
  110. package/packages/cache/node_modules/.bin/tsup-node +17 -0
  111. package/packages/cache/node_modules/.bin/tsx +17 -0
  112. package/packages/cache/node_modules/.bin/vitest +17 -0
  113. package/packages/cache/package.json +48 -0
  114. package/packages/cache/src/client/index.ts +56 -0
  115. package/packages/cache/src/errors/index.ts +53 -0
  116. package/packages/cache/src/index.ts +3 -0
  117. package/packages/cache/src/service/index.test.ts +263 -0
  118. package/packages/cache/src/service/index.ts +678 -0
  119. package/packages/cache/tsconfig.json +13 -0
  120. package/packages/cache/tsup.config.ts +13 -0
  121. package/packages/cache/vitest.config.ts +16 -0
  122. package/packages/config/.turbo/turbo-build.log +18 -0
  123. package/packages/config/dist/index.cjs +1037 -0
  124. package/packages/config/dist/index.cjs.map +1 -0
  125. package/packages/config/dist/index.d.ts +131 -0
  126. package/packages/config/node_modules/.bin/jiti +17 -0
  127. package/packages/config/node_modules/.bin/tsc +17 -0
  128. package/packages/config/node_modules/.bin/tsserver +17 -0
  129. package/packages/config/node_modules/.bin/tsup +17 -0
  130. package/packages/config/node_modules/.bin/tsup-node +17 -0
  131. package/packages/config/node_modules/.bin/tsx +17 -0
  132. package/packages/config/package.json +21 -0
  133. package/packages/config/src/index.js +1 -0
  134. package/packages/config/src/index.ts +1282 -0
  135. package/packages/config/tsconfig.json +4 -0
  136. package/packages/rpc/.turbo/turbo-build.log +32 -0
  137. package/packages/rpc/dist/_esm-BCLXDO2R.cjs +3660 -0
  138. package/packages/rpc/dist/_esm-BCLXDO2R.cjs.map +1 -0
  139. package/packages/rpc/dist/ccip-OWJLAW55.cjs +16 -0
  140. package/packages/rpc/dist/ccip-OWJLAW55.cjs.map +1 -0
  141. package/packages/rpc/dist/chunk-APQIFZ3B.cjs +6247 -0
  142. package/packages/rpc/dist/chunk-APQIFZ3B.cjs.map +1 -0
  143. package/packages/rpc/dist/chunk-CDO2GWRD.cjs +410 -0
  144. package/packages/rpc/dist/chunk-CDO2GWRD.cjs.map +1 -0
  145. package/packages/rpc/dist/chunk-QGTNTFJ7.cjs +2249 -0
  146. package/packages/rpc/dist/chunk-QGTNTFJ7.cjs.map +1 -0
  147. package/packages/rpc/dist/chunk-TZDTAHWR.cjs +44 -0
  148. package/packages/rpc/dist/chunk-TZDTAHWR.cjs.map +1 -0
  149. package/packages/rpc/dist/index.cjs +7342 -0
  150. package/packages/rpc/dist/index.cjs.map +1 -0
  151. package/packages/rpc/dist/index.d.ts +3857 -0
  152. package/packages/rpc/dist/secp256k1-WCNM675D.cjs +18 -0
  153. package/packages/rpc/dist/secp256k1-WCNM675D.cjs.map +1 -0
  154. package/packages/rpc/node_modules/.bin/jiti +17 -0
  155. package/packages/rpc/node_modules/.bin/tsc +17 -0
  156. package/packages/rpc/node_modules/.bin/tsserver +17 -0
  157. package/packages/rpc/node_modules/.bin/tsup +17 -0
  158. package/packages/rpc/node_modules/.bin/tsup-node +17 -0
  159. package/packages/rpc/node_modules/.bin/tsx +17 -0
  160. package/packages/rpc/package.json +25 -0
  161. package/packages/rpc/src/index.ts +206 -0
  162. package/packages/rpc/tsconfig.json +4 -0
  163. package/packages/typescript/base.json +36 -0
  164. package/packages/typescript/nextjs.json +17 -0
  165. package/packages/typescript/package.json +10 -0
  166. package/packages/ui/.turbo/turbo-build.log +44 -0
  167. package/packages/ui/dist/chunk-MOAFBKSA.js +11 -0
  168. package/packages/ui/dist/chunk-MOAFBKSA.js.map +1 -0
  169. package/packages/ui/dist/components/badge.d.ts +12 -0
  170. package/packages/ui/dist/components/badge.js +31 -0
  171. package/packages/ui/dist/components/badge.js.map +1 -0
  172. package/packages/ui/dist/components/button.d.ts +13 -0
  173. package/packages/ui/dist/components/button.js +40 -0
  174. package/packages/ui/dist/components/button.js.map +1 -0
  175. package/packages/ui/dist/components/card.d.ts +10 -0
  176. package/packages/ui/dist/components/card.js +39 -0
  177. package/packages/ui/dist/components/card.js.map +1 -0
  178. package/packages/ui/dist/components/input.d.ts +5 -0
  179. package/packages/ui/dist/components/input.js +28 -0
  180. package/packages/ui/dist/components/input.js.map +1 -0
  181. package/packages/ui/dist/components/label.d.ts +5 -0
  182. package/packages/ui/dist/components/label.js +13 -0
  183. package/packages/ui/dist/components/label.js.map +1 -0
  184. package/packages/ui/dist/components/separator.d.ts +5 -0
  185. package/packages/ui/dist/components/separator.js +13 -0
  186. package/packages/ui/dist/components/separator.js.map +1 -0
  187. package/packages/ui/dist/components/textarea.d.ts +5 -0
  188. package/packages/ui/dist/components/textarea.js +27 -0
  189. package/packages/ui/dist/components/textarea.js.map +1 -0
  190. package/packages/ui/dist/tailwind.d.ts +56 -0
  191. package/packages/ui/dist/tailwind.js +60 -0
  192. package/packages/ui/dist/tailwind.js.map +1 -0
  193. package/packages/ui/dist/utils/cn.d.ts +5 -0
  194. package/packages/ui/dist/utils/cn.js +7 -0
  195. package/packages/ui/dist/utils/cn.js.map +1 -0
  196. package/packages/ui/node_modules/.bin/jiti +17 -0
  197. package/packages/ui/node_modules/.bin/tsc +17 -0
  198. package/packages/ui/node_modules/.bin/tsserver +17 -0
  199. package/packages/ui/node_modules/.bin/tsup +17 -0
  200. package/packages/ui/node_modules/.bin/tsup-node +17 -0
  201. package/packages/ui/node_modules/.bin/tsx +17 -0
  202. package/packages/ui/package.json +69 -0
  203. package/packages/ui/src/components/badge.tsx +27 -0
  204. package/packages/ui/src/components/button.tsx +40 -0
  205. package/packages/ui/src/components/card.tsx +31 -0
  206. package/packages/ui/src/components/input.tsx +21 -0
  207. package/packages/ui/src/components/label.tsx +6 -0
  208. package/packages/ui/src/components/separator.tsx +6 -0
  209. package/packages/ui/src/components/textarea.tsx +20 -0
  210. package/packages/ui/src/globals.css +70 -0
  211. package/packages/ui/src/tailwind.ts +56 -0
  212. package/packages/ui/src/utils/cn.ts +6 -0
  213. package/packages/ui/tsconfig.json +20 -0
  214. package/packages/ui/tsup.config.ts +20 -0
  215. package/pnpm-workspace.yaml +4 -0
  216. package/scripts/install-rust-binaries.mjs +84 -0
  217. package/scripts/launchd/install-user-daemon.sh +358 -0
  218. package/scripts/launchd/run-vault-daemon.sh +5 -0
  219. package/scripts/launchd/run-wlfi-agent-daemon.sh +73 -0
  220. package/scripts/launchd/uninstall-user-daemon.sh +103 -0
  221. package/src/cli.ts +2121 -0
  222. package/src/lib/admin-guard.js +1 -0
  223. package/src/lib/admin-guard.ts +185 -0
  224. package/src/lib/admin-passthrough.ts +33 -0
  225. package/src/lib/admin-reset.ts +751 -0
  226. package/src/lib/admin-setup.ts +1612 -0
  227. package/src/lib/agent-auth-clear.js +1 -0
  228. package/src/lib/agent-auth-clear.ts +58 -0
  229. package/src/lib/agent-auth-forwarding.js +1 -0
  230. package/src/lib/agent-auth-forwarding.ts +149 -0
  231. package/src/lib/agent-auth-migrate.js +1 -0
  232. package/src/lib/agent-auth-migrate.ts +150 -0
  233. package/src/lib/agent-auth-revoke.ts +103 -0
  234. package/src/lib/agent-auth-rotate.ts +107 -0
  235. package/src/lib/agent-auth-token.js +1 -0
  236. package/src/lib/agent-auth-token.ts +25 -0
  237. package/src/lib/agent-auth.ts +89 -0
  238. package/src/lib/asset-broadcast.js +1 -0
  239. package/src/lib/asset-broadcast.ts +285 -0
  240. package/src/lib/bootstrap-artifacts.js +1 -0
  241. package/src/lib/bootstrap-artifacts.ts +205 -0
  242. package/src/lib/bootstrap-credentials.js +1 -0
  243. package/src/lib/bootstrap-credentials.ts +832 -0
  244. package/src/lib/config-amounts.js +1 -0
  245. package/src/lib/config-amounts.ts +189 -0
  246. package/src/lib/config-mutation.ts +27 -0
  247. package/src/lib/fs-trust.js +1 -0
  248. package/src/lib/fs-trust.ts +537 -0
  249. package/src/lib/keychain.js +1 -0
  250. package/src/lib/keychain.ts +225 -0
  251. package/src/lib/local-admin-access.ts +106 -0
  252. package/src/lib/network-selection.js +1 -0
  253. package/src/lib/network-selection.ts +71 -0
  254. package/src/lib/passthrough-security.js +1 -0
  255. package/src/lib/passthrough-security.ts +114 -0
  256. package/src/lib/rpc-guard.js +1 -0
  257. package/src/lib/rpc-guard.ts +7 -0
  258. package/src/lib/rust-spawn-options.js +1 -0
  259. package/src/lib/rust-spawn-options.ts +98 -0
  260. package/src/lib/rust.js +1 -0
  261. package/src/lib/rust.ts +143 -0
  262. package/src/lib/signed-tx.js +1 -0
  263. package/src/lib/signed-tx.ts +116 -0
  264. package/src/lib/status-repair-cli.ts +116 -0
  265. package/src/lib/sudo.js +1 -0
  266. package/src/lib/sudo.ts +172 -0
  267. package/src/lib/vault-password-forwarding.js +1 -0
  268. package/src/lib/vault-password-forwarding.ts +155 -0
  269. package/src/lib/wallet-profile.js +1 -0
  270. package/src/lib/wallet-profile.ts +332 -0
  271. package/src/lib/wallet-repair.js +1 -0
  272. package/src/lib/wallet-repair.ts +304 -0
  273. package/src/lib/wallet-setup.js +1 -0
  274. package/src/lib/wallet-setup.ts +1466 -0
  275. package/src/lib/wallet-status.js +1 -0
  276. package/src/lib/wallet-status.ts +640 -0
  277. package/tsconfig.base.json +17 -0
  278. package/tsconfig.json +10 -0
  279. package/tsup.config.ts +25 -0
  280. package/turbo.json +41 -0
  281. package/LICENSE.md +0 -1
  282. package/dist/wlfa/index.cjs +0 -250
  283. package/dist/wlfa/index.d.cts +0 -1
  284. package/dist/wlfa/index.d.ts +0 -1
  285. package/dist/wlfa/index.js +0 -250
  286. package/dist/wlfc/index.cjs +0 -1839
  287. package/dist/wlfc/index.d.cts +0 -1
  288. package/dist/wlfc/index.d.ts +0 -1
  289. package/dist/wlfc/index.js +0 -1839
@@ -0,0 +1,301 @@
1
+ use time::{Duration, OffsetDateTime};
2
+ use uuid::Uuid;
3
+ use vault_domain::{AgentAction, PolicyAttachment, PolicyType, SpendEvent, SpendingPolicy};
4
+
5
+ use crate::{PolicyDecision, PolicyError, PolicyEvaluation, PolicyExplanation};
6
+
7
+ /// Stateless policy evaluator.
8
+ #[derive(Debug, Default)]
9
+ pub struct PolicyEngine;
10
+
11
+ impl PolicyEngine {
12
+ /// Evaluates all applicable policies.
13
+ ///
14
+ /// A request is approved only if every applicable policy passes.
15
+ pub fn evaluate(
16
+ &self,
17
+ policies: &[SpendingPolicy],
18
+ attachment: &PolicyAttachment,
19
+ action: &AgentAction,
20
+ spend_history: &[SpendEvent],
21
+ agent_key_id: Uuid,
22
+ now: OffsetDateTime,
23
+ ) -> Result<PolicyEvaluation, PolicyError> {
24
+ let explanation = self.explain(
25
+ policies,
26
+ attachment,
27
+ action,
28
+ spend_history,
29
+ agent_key_id,
30
+ now,
31
+ );
32
+ match explanation.decision {
33
+ PolicyDecision::Allow => Ok(PolicyEvaluation {
34
+ evaluated_policy_ids: explanation.evaluated_policy_ids,
35
+ }),
36
+ PolicyDecision::Deny(err) => Err(err),
37
+ }
38
+ }
39
+
40
+ /// Builds a detailed explanation for policy evaluation.
41
+ #[must_use]
42
+ pub fn explain(
43
+ &self,
44
+ policies: &[SpendingPolicy],
45
+ attachment: &PolicyAttachment,
46
+ action: &AgentAction,
47
+ spend_history: &[SpendEvent],
48
+ agent_key_id: Uuid,
49
+ now: OffsetDateTime,
50
+ ) -> PolicyExplanation {
51
+ let mut attached: Vec<&SpendingPolicy> = policies
52
+ .iter()
53
+ .filter(|p| p.enabled && attachment.applies_to(p.id))
54
+ .collect();
55
+ attached.sort_by(|a, b| a.priority.cmp(&b.priority).then_with(|| a.id.cmp(&b.id)));
56
+ let attached_policy_ids = attached.iter().map(|p| p.id).collect::<Vec<_>>();
57
+ if attached.is_empty() {
58
+ return PolicyExplanation {
59
+ attached_policy_ids,
60
+ applicable_policy_ids: Vec::new(),
61
+ evaluated_policy_ids: Vec::new(),
62
+ decision: PolicyDecision::Deny(PolicyError::NoAttachedPolicies),
63
+ };
64
+ }
65
+
66
+ let action_asset = action.asset();
67
+ let action_recipient = action.recipient();
68
+ let action_chain_id = action.chain_id();
69
+
70
+ let applicable: Vec<&SpendingPolicy> = attached
71
+ .into_iter()
72
+ .filter(|p| {
73
+ p.assets.allows(&action_asset)
74
+ && p.recipients.allows(&action_recipient)
75
+ && p.networks.allows(&action_chain_id)
76
+ })
77
+ .collect();
78
+ let applicable_policy_ids = applicable.iter().map(|p| p.id).collect::<Vec<_>>();
79
+ if applicable.is_empty() {
80
+ return PolicyExplanation {
81
+ attached_policy_ids,
82
+ applicable_policy_ids,
83
+ evaluated_policy_ids: Vec::new(),
84
+ decision: PolicyDecision::Deny(PolicyError::NoApplicablePolicies),
85
+ };
86
+ }
87
+
88
+ let mut evaluated_policy_ids = Vec::with_capacity(applicable.len());
89
+ for policy in applicable {
90
+ evaluated_policy_ids.push(policy.id);
91
+ if let Err(err) =
92
+ self.evaluate_single_policy(policy, action, spend_history, agent_key_id, now)
93
+ {
94
+ return PolicyExplanation {
95
+ attached_policy_ids,
96
+ applicable_policy_ids,
97
+ evaluated_policy_ids,
98
+ decision: PolicyDecision::Deny(err),
99
+ };
100
+ }
101
+ }
102
+
103
+ PolicyExplanation {
104
+ attached_policy_ids,
105
+ applicable_policy_ids,
106
+ evaluated_policy_ids,
107
+ decision: PolicyDecision::Allow,
108
+ }
109
+ }
110
+
111
+ fn evaluate_single_policy(
112
+ &self,
113
+ policy: &SpendingPolicy,
114
+ action: &AgentAction,
115
+ spend_history: &[SpendEvent],
116
+ agent_key_id: Uuid,
117
+ now: OffsetDateTime,
118
+ ) -> Result<(), PolicyError> {
119
+ let requested_amount_wei = action.amount_wei();
120
+
121
+ match policy.policy_type {
122
+ PolicyType::PerTxMaxSpending => {
123
+ if requested_amount_wei > policy.max_amount_wei {
124
+ return Err(PolicyError::PerTxLimitExceeded {
125
+ policy_id: policy.id,
126
+ max_amount_wei: policy.max_amount_wei,
127
+ requested_amount_wei,
128
+ });
129
+ }
130
+ }
131
+ PolicyType::PerTxMaxFeePerGas => {
132
+ if let Some(requested_max_fee_per_gas_wei) = action.max_fee_per_gas_wei() {
133
+ if requested_max_fee_per_gas_wei > policy.max_amount_wei {
134
+ return Err(PolicyError::MaxFeePerGasLimitExceeded {
135
+ policy_id: policy.id,
136
+ max_fee_per_gas_wei: policy.max_amount_wei,
137
+ requested_max_fee_per_gas_wei,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ PolicyType::PerTxMaxPriorityFeePerGas => {
143
+ if let Some(requested_max_priority_fee_per_gas_wei) =
144
+ action.max_priority_fee_per_gas_wei()
145
+ {
146
+ if requested_max_priority_fee_per_gas_wei > policy.max_amount_wei {
147
+ return Err(PolicyError::PriorityFeePerGasLimitExceeded {
148
+ policy_id: policy.id,
149
+ max_priority_fee_per_gas_wei: policy.max_amount_wei,
150
+ requested_max_priority_fee_per_gas_wei,
151
+ });
152
+ }
153
+ }
154
+ }
155
+ PolicyType::PerTxMaxCalldataBytes => {
156
+ if let Some(requested_calldata_bytes) = action.calldata_len_bytes() {
157
+ let requested_calldata_bytes = requested_calldata_bytes as u128;
158
+ if requested_calldata_bytes > policy.max_amount_wei {
159
+ return Err(PolicyError::CalldataBytesLimitExceeded {
160
+ policy_id: policy.id,
161
+ max_calldata_bytes: policy.max_amount_wei,
162
+ requested_calldata_bytes,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ PolicyType::DailyMaxSpending => self.enforce_window_limit(
168
+ policy,
169
+ spend_history,
170
+ agent_key_id,
171
+ now - Duration::days(1),
172
+ requested_amount_wei,
173
+ )?,
174
+ PolicyType::DailyMaxTxCount => {
175
+ self.enforce_tx_count_window(
176
+ policy,
177
+ spend_history,
178
+ agent_key_id,
179
+ now - Duration::days(1),
180
+ )?;
181
+ }
182
+ PolicyType::WeeklyMaxSpending => self.enforce_window_limit(
183
+ policy,
184
+ spend_history,
185
+ agent_key_id,
186
+ now - Duration::weeks(1),
187
+ requested_amount_wei,
188
+ )?,
189
+ PolicyType::PerChainMaxGasSpend => {
190
+ if let Some(requested_gas_wei) = action.max_gas_spend_wei() {
191
+ if requested_gas_wei > policy.max_amount_wei {
192
+ return Err(PolicyError::GasLimitExceeded {
193
+ policy_id: policy.id,
194
+ max_gas_wei: policy.max_amount_wei,
195
+ requested_gas_wei,
196
+ });
197
+ }
198
+ }
199
+ }
200
+ PolicyType::ManualApproval => {
201
+ let min_amount_wei = policy.min_amount_wei.unwrap_or(1);
202
+ if requested_amount_wei >= min_amount_wei
203
+ && requested_amount_wei <= policy.max_amount_wei
204
+ {
205
+ return Err(PolicyError::ManualApprovalRequired {
206
+ policy_id: policy.id,
207
+ min_amount_wei: policy.min_amount_wei,
208
+ max_amount_wei: policy.max_amount_wei,
209
+ requested_amount_wei,
210
+ });
211
+ }
212
+ }
213
+ }
214
+
215
+ Ok(())
216
+ }
217
+
218
+ fn enforce_tx_count_window(
219
+ &self,
220
+ policy: &SpendingPolicy,
221
+ spend_history: &[SpendEvent],
222
+ agent_key_id: Uuid,
223
+ window_start: OffsetDateTime,
224
+ ) -> Result<(), PolicyError> {
225
+ let mut used_tx_count = 0u128;
226
+ for _event in spend_history.iter().filter(|event| {
227
+ event.agent_key_id == agent_key_id
228
+ && event.at >= window_start
229
+ && policy.assets.allows(&event.asset)
230
+ && policy.recipients.allows(&event.recipient)
231
+ && policy.networks.allows(&event.chain_id)
232
+ }) {
233
+ match used_tx_count.checked_add(1) {
234
+ Some(next) => used_tx_count = next,
235
+ None => {
236
+ used_tx_count = u128::MAX;
237
+ break;
238
+ }
239
+ }
240
+ }
241
+
242
+ let exceeds_window = used_tx_count
243
+ .checked_add(1)
244
+ .is_none_or(|total| total > policy.max_amount_wei);
245
+ if exceeds_window {
246
+ return Err(PolicyError::TxCountLimitExceeded {
247
+ policy_id: policy.id,
248
+ used_tx_count,
249
+ max_tx_count: policy.max_amount_wei,
250
+ });
251
+ }
252
+
253
+ Ok(())
254
+ }
255
+
256
+ fn enforce_window_limit(
257
+ &self,
258
+ policy: &SpendingPolicy,
259
+ spend_history: &[SpendEvent],
260
+ agent_key_id: Uuid,
261
+ window_start: OffsetDateTime,
262
+ requested_amount_wei: u128,
263
+ ) -> Result<(), PolicyError> {
264
+ let mut used_amount_wei = 0u128;
265
+ let mut overflowed = false;
266
+ for event in spend_history.iter().filter(|event| {
267
+ event.agent_key_id == agent_key_id
268
+ && event.at >= window_start
269
+ && policy.assets.allows(&event.asset)
270
+ && policy.recipients.allows(&event.recipient)
271
+ && policy.networks.allows(&event.chain_id)
272
+ }) {
273
+ match used_amount_wei.checked_add(event.amount_wei) {
274
+ Some(next) => used_amount_wei = next,
275
+ None => {
276
+ // Overflow indicates history already exceeded representable
277
+ // accounting bounds. Reject instead of undercounting.
278
+ overflowed = true;
279
+ used_amount_wei = u128::MAX;
280
+ break;
281
+ }
282
+ }
283
+ }
284
+
285
+ let exceeds_window = overflowed
286
+ || used_amount_wei
287
+ .checked_add(requested_amount_wei)
288
+ .is_none_or(|total| total > policy.max_amount_wei);
289
+
290
+ if exceeds_window {
291
+ return Err(PolicyError::WindowLimitExceeded {
292
+ policy_id: policy.id,
293
+ used_amount_wei,
294
+ requested_amount_wei,
295
+ max_amount_wei: policy.max_amount_wei,
296
+ });
297
+ }
298
+
299
+ Ok(())
300
+ }
301
+ }
@@ -0,0 +1,81 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use thiserror::Error;
3
+ use uuid::Uuid;
4
+
5
+ #[derive(Debug, Error, Clone, PartialEq, Eq, Serialize, Deserialize)]
6
+ pub enum PolicyError {
7
+ #[error("no enabled policies attached to this agent key")]
8
+ NoAttachedPolicies,
9
+ #[error("no policy scope allows recipient/asset/network for this action")]
10
+ NoApplicablePolicies,
11
+ #[error(
12
+ "policy {policy_id} rejected request: per transaction max {max_amount_wei} < requested {requested_amount_wei}"
13
+ )]
14
+ PerTxLimitExceeded {
15
+ policy_id: Uuid,
16
+ max_amount_wei: u128,
17
+ requested_amount_wei: u128,
18
+ },
19
+ #[error(
20
+ "policy {policy_id} rejected request: window usage {used_amount_wei} + requested {requested_amount_wei} > max {max_amount_wei}"
21
+ )]
22
+ WindowLimitExceeded {
23
+ policy_id: Uuid,
24
+ used_amount_wei: u128,
25
+ requested_amount_wei: u128,
26
+ max_amount_wei: u128,
27
+ },
28
+ #[error(
29
+ "policy {policy_id} rejected request: gas max {max_gas_wei} < requested {requested_gas_wei}"
30
+ )]
31
+ GasLimitExceeded {
32
+ policy_id: Uuid,
33
+ max_gas_wei: u128,
34
+ requested_gas_wei: u128,
35
+ },
36
+ #[error(
37
+ "policy {policy_id} rejected request: max_fee_per_gas_wei {max_fee_per_gas_wei} < requested {requested_max_fee_per_gas_wei}"
38
+ )]
39
+ MaxFeePerGasLimitExceeded {
40
+ policy_id: Uuid,
41
+ max_fee_per_gas_wei: u128,
42
+ requested_max_fee_per_gas_wei: u128,
43
+ },
44
+ #[error(
45
+ "policy {policy_id} rejected request: max_priority_fee_per_gas_wei {max_priority_fee_per_gas_wei} < requested {requested_max_priority_fee_per_gas_wei}"
46
+ )]
47
+ PriorityFeePerGasLimitExceeded {
48
+ policy_id: Uuid,
49
+ max_priority_fee_per_gas_wei: u128,
50
+ requested_max_priority_fee_per_gas_wei: u128,
51
+ },
52
+ #[error(
53
+ "policy {policy_id} rejected request: calldata max bytes {max_calldata_bytes} < requested {requested_calldata_bytes}"
54
+ )]
55
+ CalldataBytesLimitExceeded {
56
+ policy_id: Uuid,
57
+ max_calldata_bytes: u128,
58
+ requested_calldata_bytes: u128,
59
+ },
60
+ #[error(
61
+ "policy {policy_id} rejected request: tx_count usage {used_tx_count} + 1 > max {max_tx_count}"
62
+ )]
63
+ TxCountLimitExceeded {
64
+ policy_id: Uuid,
65
+ used_tx_count: u128,
66
+ max_tx_count: u128,
67
+ },
68
+ #[error(
69
+ "policy {policy_id} rejected request: missing required transaction metadata ({metadata})"
70
+ )]
71
+ MissingTransactionMetadata { policy_id: Uuid, metadata: String },
72
+ #[error(
73
+ "policy {policy_id} requires manual approval for requested amount {requested_amount_wei} within range {min_amount_wei:?}..={max_amount_wei}"
74
+ )]
75
+ ManualApprovalRequired {
76
+ policy_id: Uuid,
77
+ min_amount_wei: Option<u128>,
78
+ max_amount_wei: u128,
79
+ requested_amount_wei: u128,
80
+ },
81
+ }
@@ -0,0 +1,17 @@
1
+ //! Policy evaluation engine for agent signing requests.
2
+
3
+ #![forbid(unsafe_code)]
4
+
5
+ mod engine;
6
+ mod error;
7
+ mod report;
8
+
9
+ pub use engine::PolicyEngine;
10
+ pub use error::PolicyError;
11
+ pub use report::{PolicyDecision, PolicyEvaluation, PolicyExplanation};
12
+
13
+ #[cfg(test)]
14
+ mod tests;
15
+
16
+ #[cfg(test)]
17
+ mod tests_explain;
@@ -0,0 +1,34 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use uuid::Uuid;
3
+
4
+ use crate::PolicyError;
5
+
6
+ /// Successful policy evaluation details.
7
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8
+ pub struct PolicyEvaluation {
9
+ /// Policies evaluated (priority order, low to high numeric priority).
10
+ pub evaluated_policy_ids: Vec<Uuid>,
11
+ }
12
+
13
+ /// Policy decision for an explanation report.
14
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15
+ #[serde(tag = "decision", content = "reason", rename_all = "snake_case")]
16
+ pub enum PolicyDecision {
17
+ /// Request satisfies all applicable policies.
18
+ Allow,
19
+ /// Request was denied by policy engine.
20
+ Deny(PolicyError),
21
+ }
22
+
23
+ /// Full policy explanation report.
24
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25
+ pub struct PolicyExplanation {
26
+ /// Enabled policy ids attached to the agent (priority order).
27
+ pub attached_policy_ids: Vec<Uuid>,
28
+ /// Attached policy ids whose recipient/asset/network scope matched the action.
29
+ pub applicable_policy_ids: Vec<Uuid>,
30
+ /// Policy ids evaluated in order up to decision point.
31
+ pub evaluated_policy_ids: Vec<Uuid>,
32
+ /// Final allow/deny decision.
33
+ pub decision: PolicyDecision,
34
+ }