actshield 0.9.0__py3-none-any.whl

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 (366) hide show
  1. actshield/__init__.py +439 -0
  2. actshield/__main__.py +46 -0
  3. actshield/agents/__init__.py +12 -0
  4. actshield/agents/agent.py +200 -0
  5. actshield/agents/identity.py +114 -0
  6. actshield/api/__init__.py +34 -0
  7. actshield/api/models.py +198 -0
  8. actshield/api/server.py +669 -0
  9. actshield/api/service.py +599 -0
  10. actshield/approval/__init__.py +15 -0
  11. actshield/approval/approval.py +164 -0
  12. actshield/cli/__init__.py +8 -0
  13. actshield/cli/graph.py +49 -0
  14. actshield/cli/main.py +802 -0
  15. actshield/cli/shell.py +406 -0
  16. actshield/cli/theme.py +110 -0
  17. actshield/client.py +757 -0
  18. actshield/config.py +88 -0
  19. actshield/context/__init__.py +14 -0
  20. actshield/context/context.py +161 -0
  21. actshield/context/provenance.py +108 -0
  22. actshield/context/taint.py +48 -0
  23. actshield/decisions/decision.py +74 -0
  24. actshield/delegation/__init__.py +11 -0
  25. actshield/delegation/authority.py +63 -0
  26. actshield/delegation/delegation.py +180 -0
  27. actshield/demo.py +139 -0
  28. actshield/doctor.py +94 -0
  29. actshield/drift/__init__.py +27 -0
  30. actshield/drift/drift_engine.py +149 -0
  31. actshield/drift/drift_models.py +63 -0
  32. actshield/evaluation/__init__.py +1 -0
  33. actshield/evaluation/cases/__init__.py +1 -0
  34. actshield/evaluation/eval_suite.py +254 -0
  35. actshield/forensics/__init__.py +53 -0
  36. actshield/forensics/access_graph.py +216 -0
  37. actshield/forensics/authority_graph.py +186 -0
  38. actshield/forensics/delegation_graph.py +119 -0
  39. actshield/forensics/explanation.py +254 -0
  40. actshield/forensics/models.py +488 -0
  41. actshield/forensics/queries.py +367 -0
  42. actshield/forensics/serializers.py +68 -0
  43. actshield/forensics/service.py +310 -0
  44. actshield/forensics/snapshots.py +174 -0
  45. actshield/gates/__init__.py +13 -0
  46. actshield/gates/gate_models.py +46 -0
  47. actshield/gates/security_gate.py +191 -0
  48. actshield/gateway/__init__.py +13 -0
  49. actshield/gateway/http.py +191 -0
  50. actshield/gateway/mcp.py +292 -0
  51. actshield/incidents/__init__.py +25 -0
  52. actshield/incidents/incident.py +94 -0
  53. actshield/incidents/incident_engine.py +163 -0
  54. actshield/incidents/incident_rules.py +116 -0
  55. actshield/incidents/incident_state.py +58 -0
  56. actshield/integrations/__init__.py +24 -0
  57. actshield/integrations/ai_secura.py +169 -0
  58. actshield/integrations/ai_secura_analysis.py +236 -0
  59. actshield/integrations/apiris.py +122 -0
  60. actshield/integrations/few_shot.py +104 -0
  61. actshield/integrations/ollama_adapter.py +538 -0
  62. actshield/integrations/prompts/__init__.py +1 -0
  63. actshield/integrations/prompts/examples/__init__.py +1 -0
  64. actshield/integrations/security_packet.py +82 -0
  65. actshield/llm_config.py +89 -0
  66. actshield/offensive/__init__.py +95 -0
  67. actshield/offensive/adaptive/__init__.py +22 -0
  68. actshield/offensive/adaptive/adaptive_engine.py +165 -0
  69. actshield/offensive/adaptive/attack_selector.py +51 -0
  70. actshield/offensive/adaptive/boundary_search.py +99 -0
  71. actshield/offensive/adaptive/campaign_memory.py +123 -0
  72. actshield/offensive/adaptive/feedback.py +84 -0
  73. actshield/offensive/adaptive/mutation_planner.py +57 -0
  74. actshield/offensive/attack.py +78 -0
  75. actshield/offensive/campaign.py +219 -0
  76. actshield/offensive/corpus.py +155 -0
  77. actshield/offensive/engine.py +466 -0
  78. actshield/offensive/evaluator.py +116 -0
  79. actshield/offensive/evidence.py +63 -0
  80. actshield/offensive/generator.py +128 -0
  81. actshield/offensive/mutation.py +101 -0
  82. actshield/offensive/results.py +77 -0
  83. actshield/offensive/safety.py +80 -0
  84. actshield/offensive/strategies/__init__.py +22 -0
  85. actshield/offensive/strategies/authority_escalation.py +100 -0
  86. actshield/offensive/strategies/base.py +34 -0
  87. actshield/offensive/strategies/context_manipulation.py +100 -0
  88. actshield/offensive/strategies/data_exfiltration.py +100 -0
  89. actshield/offensive/strategies/delegation_attacks.py +100 -0
  90. actshield/offensive/strategies/mcp_attacks.py +100 -0
  91. actshield/offensive/strategies/prompt_injection.py +100 -0
  92. actshield/offensive/strategies/tool_poisoning.py +100 -0
  93. actshield/offensive/target.py +113 -0
  94. actshield/offensive/vulnerable_target.py +121 -0
  95. actshield/persistence/__init__.py +18 -0
  96. actshield/persistence/siem.py +102 -0
  97. actshield/persistence/storage.py +240 -0
  98. actshield/policy/__init__.py +12 -0
  99. actshield/policy/evaluator.py +294 -0
  100. actshield/policy/intent.py +113 -0
  101. actshield/policy/policy.py +48 -0
  102. actshield/posture/__init__.py +26 -0
  103. actshield/posture/posture_engine.py +186 -0
  104. actshield/posture/posture_models.py +152 -0
  105. actshield/posture/posture_rules.py +137 -0
  106. actshield/providers/__init__.py +32 -0
  107. actshield/providers/anthropic_provider.py +146 -0
  108. actshield/providers/base.py +160 -0
  109. actshield/providers/gemini_provider.py +133 -0
  110. actshield/providers/null_provider.py +40 -0
  111. actshield/providers/ollama_provider.py +135 -0
  112. actshield/providers/openai_provider.py +142 -0
  113. actshield/providers/registry.py +244 -0
  114. actshield/py.typed +0 -0
  115. actshield/response/__init__.py +14 -0
  116. actshield/response/response_actions.py +39 -0
  117. actshield/response/response_audit.py +32 -0
  118. actshield/response/response_engine.py +139 -0
  119. actshield/risk/models.py +104 -0
  120. actshield/serve.py +117 -0
  121. actshield/static/404.html +1 -0
  122. actshield/static/__next.__PAGE__.txt +24 -0
  123. actshield/static/__next._full.txt +20 -0
  124. actshield/static/__next._tree.txt +4 -0
  125. actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_buildManifest.js +11 -0
  126. actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_clientMiddlewareManifest.js +1 -0
  127. actshield/static/_next/static/Yp_swxN-C9UoCRNOwkYnY/_ssgManifest.js +1 -0
  128. actshield/static/_next/static/chunks/09kny2eqjc2or.js +1 -0
  129. actshield/static/_next/static/chunks/0c1zba1u9whdp.js +1 -0
  130. actshield/static/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  131. actshield/static/_next/static/chunks/0dbjjeg01x25o.js +1 -0
  132. actshield/static/_next/static/chunks/0duobs55ne238.js +1 -0
  133. actshield/static/_next/static/chunks/0iaz41l6068e4.js +1 -0
  134. actshield/static/_next/static/chunks/0mjhyv72x2idp.js +1 -0
  135. actshield/static/_next/static/chunks/0nelpqfu_ciq6.js +1 -0
  136. actshield/static/_next/static/chunks/0p1bci9ch797v.js +1 -0
  137. actshield/static/_next/static/chunks/0tld_ygho5tgm.js +1 -0
  138. actshield/static/_next/static/chunks/0w1-y7oni66j9.js +1 -0
  139. actshield/static/_next/static/chunks/10537zyl427zj.js +1 -0
  140. actshield/static/_next/static/chunks/11pc_jkl3aggn.js +1 -0
  141. actshield/static/_next/static/chunks/16k7nw0b3t8ys.js +1 -0
  142. actshield/static/_next/static/chunks/19mx3mg6lkumu.js +1 -0
  143. actshield/static/_next/static/chunks/1_8p47b-kxg2a.js +1 -0
  144. actshield/static/_next/static/chunks/1dszjozwfqegx.js +1 -0
  145. actshield/static/_next/static/chunks/1h43jcb90-m2r.js +22 -0
  146. actshield/static/_next/static/chunks/1ly04cvt_wh_j.js +1 -0
  147. actshield/static/_next/static/chunks/1odxrypuwuwp_.js +1 -0
  148. actshield/static/_next/static/chunks/1oes4c35qz5ku.js +1 -0
  149. actshield/static/_next/static/chunks/1ow883jl4ln-r.js +16 -0
  150. actshield/static/_next/static/chunks/1po7bna1as8tc.js +1 -0
  151. actshield/static/_next/static/chunks/1rsh0og8ifkzx.js +1 -0
  152. actshield/static/_next/static/chunks/1v0lwzv-859z_.js +1 -0
  153. actshield/static/_next/static/chunks/25iat2epwx25a.js +1 -0
  154. actshield/static/_next/static/chunks/25uvt4s2f39a4.js +1 -0
  155. actshield/static/_next/static/chunks/27t_qfc-3_lzs.js +1 -0
  156. actshield/static/_next/static/chunks/28bkmy8mez7gm.js +1 -0
  157. actshield/static/_next/static/chunks/2b6brmw_drw1p.js +1 -0
  158. actshield/static/_next/static/chunks/2fnhtxi4j6vge.js +1 -0
  159. actshield/static/_next/static/chunks/2p087eftxemg4.js +6 -0
  160. actshield/static/_next/static/chunks/2s8pao_z8pgtq.js +1 -0
  161. actshield/static/_next/static/chunks/2yj7ooc2c-zpg.js +1 -0
  162. actshield/static/_next/static/chunks/3-mrrr8udt5tk.js +1 -0
  163. actshield/static/_next/static/chunks/310vm2bl3xxpt.js +31 -0
  164. actshield/static/_next/static/chunks/35mvsv251jx1r.css +3 -0
  165. actshield/static/_next/static/chunks/370vf0oetlsx9.js +1 -0
  166. actshield/static/_next/static/chunks/3e-2m72tyo556.js +1 -0
  167. actshield/static/_next/static/chunks/3fntmmi971322.js +1 -0
  168. actshield/static/_next/static/chunks/3fzw-g0hycl2c.js +1 -0
  169. actshield/static/_next/static/chunks/3lopq3lhmcjxg.js +129 -0
  170. actshield/static/_next/static/chunks/3mpe2jejel0v2.css +1 -0
  171. actshield/static/_next/static/chunks/3t8o4f2lzr2sf.js +1 -0
  172. actshield/static/_next/static/chunks/3y-v1neyllne5.js +1 -0
  173. actshield/static/_next/static/chunks/43oiwy8mx6cs1.js +1 -0
  174. actshield/static/_next/static/chunks/turbopack-23wzuebcy_lxm.js +1 -0
  175. actshield/static/_next/static/media/1bffadaabf893a1e-s.3-6t-g6q0vh0a.woff2 +0 -0
  176. actshield/static/_next/static/media/2bbe8d2671613f1f-s.0k62hbripvv8p.woff2 +0 -0
  177. actshield/static/_next/static/media/2c55a0e60120577a-s.0-dom-5bn10r2.woff2 +0 -0
  178. actshield/static/_next/static/media/5476f68d60460930-s.2uwcyprjm3xu3.woff2 +0 -0
  179. actshield/static/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2 +0 -0
  180. actshield/static/_next/static/media/9c72aa0f40e4eef8-s.1y4-pdgsjb-pw.woff2 +0 -0
  181. actshield/static/_next/static/media/ad66f9afd8947f86-s.3lvt2whj97whp.woff2 +0 -0
  182. actshield/static/_next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  183. actshield/static/_not-found/__next._full.txt +17 -0
  184. actshield/static/_not-found/__next._not-found/__PAGE__.txt +23 -0
  185. actshield/static/_not-found/__next._tree.txt +3 -0
  186. actshield/static/_not-found.html +1 -0
  187. actshield/static/_not-found.txt +17 -0
  188. actshield/static/access/matrix/__next._full.txt +24 -0
  189. actshield/static/access/matrix/__next._tree.txt +4 -0
  190. actshield/static/access/matrix/__next.access/matrix/__PAGE__.txt +30 -0
  191. actshield/static/access/matrix.html +1 -0
  192. actshield/static/access/matrix.txt +24 -0
  193. actshield/static/activity/__next._full.txt +22 -0
  194. actshield/static/activity/__next._tree.txt +4 -0
  195. actshield/static/activity/__next.activity/__PAGE__.txt +27 -0
  196. actshield/static/activity.html +1 -0
  197. actshield/static/activity.txt +22 -0
  198. actshield/static/agents/__next._full.txt +22 -0
  199. actshield/static/agents/__next._tree.txt +4 -0
  200. actshield/static/agents/__next.agents/__PAGE__.txt +27 -0
  201. actshield/static/agents.html +1 -0
  202. actshield/static/agents.txt +22 -0
  203. actshield/static/ai-secura/__next._full.txt +22 -0
  204. actshield/static/ai-secura/__next._tree.txt +4 -0
  205. actshield/static/ai-secura/__next.ai-secura/__PAGE__.txt +27 -0
  206. actshield/static/ai-secura.html +1 -0
  207. actshield/static/ai-secura.txt +22 -0
  208. actshield/static/api/__next._full.txt +22 -0
  209. actshield/static/api/__next._tree.txt +4 -0
  210. actshield/static/api/__next.api/__PAGE__.txt +27 -0
  211. actshield/static/api.html +15 -0
  212. actshield/static/api.txt +22 -0
  213. actshield/static/apiris/__next._full.txt +22 -0
  214. actshield/static/apiris/__next._tree.txt +4 -0
  215. actshield/static/apiris/__next.apiris/__PAGE__.txt +27 -0
  216. actshield/static/apiris.html +1 -0
  217. actshield/static/apiris.txt +22 -0
  218. actshield/static/attack-graph/__next._full.txt +23 -0
  219. actshield/static/attack-graph/__next._tree.txt +5 -0
  220. actshield/static/attack-graph/__next.attack-graph/__PAGE__.txt +28 -0
  221. actshield/static/attack-graph.html +2 -0
  222. actshield/static/attack-graph.txt +23 -0
  223. actshield/static/campaigns/__next._full.txt +22 -0
  224. actshield/static/campaigns/__next._tree.txt +4 -0
  225. actshield/static/campaigns/__next.campaigns/__PAGE__.txt +27 -0
  226. actshield/static/campaigns.html +1 -0
  227. actshield/static/campaigns.txt +22 -0
  228. actshield/static/context/__next._full.txt +22 -0
  229. actshield/static/context/__next._tree.txt +4 -0
  230. actshield/static/context/__next.context/__PAGE__.txt +27 -0
  231. actshield/static/context.html +1 -0
  232. actshield/static/context.txt +22 -0
  233. actshield/static/dashboard/__next._full.txt +22 -0
  234. actshield/static/dashboard/__next._tree.txt +4 -0
  235. actshield/static/dashboard/__next.dashboard/__PAGE__.txt +27 -0
  236. actshield/static/dashboard.html +1 -0
  237. actshield/static/dashboard.txt +22 -0
  238. actshield/static/delegations/__next._full.txt +22 -0
  239. actshield/static/delegations/__next._tree.txt +4 -0
  240. actshield/static/delegations/__next.delegations/__PAGE__.txt +27 -0
  241. actshield/static/delegations.html +1 -0
  242. actshield/static/delegations.txt +22 -0
  243. actshield/static/demo/__next._full.txt +22 -0
  244. actshield/static/demo/__next._tree.txt +4 -0
  245. actshield/static/demo/__next.demo/__PAGE__.txt +27 -0
  246. actshield/static/demo.html +1 -0
  247. actshield/static/demo.txt +22 -0
  248. actshield/static/drift/__next._full.txt +22 -0
  249. actshield/static/drift/__next._tree.txt +4 -0
  250. actshield/static/drift/__next.drift/__PAGE__.txt +27 -0
  251. actshield/static/drift.html +1 -0
  252. actshield/static/drift.txt +22 -0
  253. actshield/static/favicon.ico +0 -0
  254. actshield/static/file.svg +1 -0
  255. actshield/static/forensics/__next._full.txt +22 -0
  256. actshield/static/forensics/__next._tree.txt +4 -0
  257. actshield/static/forensics/__next.forensics/__PAGE__.txt +27 -0
  258. actshield/static/forensics.html +1 -0
  259. actshield/static/forensics.txt +22 -0
  260. actshield/static/globe.svg +1 -0
  261. actshield/static/incidents/__next._full.txt +22 -0
  262. actshield/static/incidents/__next._tree.txt +4 -0
  263. actshield/static/incidents/__next.incidents/__PAGE__.txt +27 -0
  264. actshield/static/incidents.html +1 -0
  265. actshield/static/incidents.txt +22 -0
  266. actshield/static/index.html +3 -0
  267. actshield/static/index.txt +20 -0
  268. actshield/static/logo.png +0 -0
  269. actshield/static/metrics/__next._full.txt +22 -0
  270. actshield/static/metrics/__next._tree.txt +4 -0
  271. actshield/static/metrics/__next.metrics/__PAGE__.txt +27 -0
  272. actshield/static/metrics.html +1 -0
  273. actshield/static/metrics.txt +22 -0
  274. actshield/static/next.svg +1 -0
  275. actshield/static/offensive/__next._full.txt +22 -0
  276. actshield/static/offensive/__next._tree.txt +4 -0
  277. actshield/static/offensive/__next.offensive/__PAGE__.txt +27 -0
  278. actshield/static/offensive.html +1 -0
  279. actshield/static/offensive.txt +22 -0
  280. actshield/static/policies/__next._full.txt +22 -0
  281. actshield/static/policies/__next._tree.txt +4 -0
  282. actshield/static/policies/__next.policies/__PAGE__.txt +27 -0
  283. actshield/static/policies.html +1 -0
  284. actshield/static/policies.txt +22 -0
  285. actshield/static/posture/__next._full.txt +22 -0
  286. actshield/static/posture/__next._tree.txt +4 -0
  287. actshield/static/posture/__next.posture/__PAGE__.txt +27 -0
  288. actshield/static/posture.html +1 -0
  289. actshield/static/posture.txt +22 -0
  290. actshield/static/regressions/__next._full.txt +22 -0
  291. actshield/static/regressions/__next._tree.txt +4 -0
  292. actshield/static/regressions/__next.regressions/__PAGE__.txt +27 -0
  293. actshield/static/regressions.html +1 -0
  294. actshield/static/regressions.txt +22 -0
  295. actshield/static/reports/__next._full.txt +22 -0
  296. actshield/static/reports/__next._tree.txt +4 -0
  297. actshield/static/reports/__next.reports/__PAGE__.txt +27 -0
  298. actshield/static/reports.html +1 -0
  299. actshield/static/reports.txt +22 -0
  300. actshield/static/resources/__next._full.txt +22 -0
  301. actshield/static/resources/__next._tree.txt +4 -0
  302. actshield/static/resources/__next.resources/__PAGE__.txt +27 -0
  303. actshield/static/resources.html +1 -0
  304. actshield/static/resources.txt +22 -0
  305. actshield/static/security-gates/__next._full.txt +22 -0
  306. actshield/static/security-gates/__next._tree.txt +4 -0
  307. actshield/static/security-gates/__next.security-gates/__PAGE__.txt +27 -0
  308. actshield/static/security-gates.html +1 -0
  309. actshield/static/security-gates.txt +22 -0
  310. actshield/static/settings/__next._full.txt +22 -0
  311. actshield/static/settings/__next._tree.txt +4 -0
  312. actshield/static/settings/__next.settings/__PAGE__.txt +27 -0
  313. actshield/static/settings.html +1 -0
  314. actshield/static/settings.txt +22 -0
  315. actshield/static/tasks/__next._full.txt +22 -0
  316. actshield/static/tasks/__next._tree.txt +4 -0
  317. actshield/static/tasks/__next.tasks/__PAGE__.txt +27 -0
  318. actshield/static/tasks.html +1 -0
  319. actshield/static/tasks.txt +22 -0
  320. actshield/static/threats/__next._full.txt +22 -0
  321. actshield/static/threats/__next._tree.txt +4 -0
  322. actshield/static/threats/__next.threats/__PAGE__.txt +27 -0
  323. actshield/static/threats.html +1 -0
  324. actshield/static/threats.txt +22 -0
  325. actshield/static/tools/__next._full.txt +22 -0
  326. actshield/static/tools/__next._tree.txt +4 -0
  327. actshield/static/tools/__next.tools/__PAGE__.txt +27 -0
  328. actshield/static/tools.html +1 -0
  329. actshield/static/tools.txt +22 -0
  330. actshield/static/traces/__next._full.txt +22 -0
  331. actshield/static/traces/__next._tree.txt +4 -0
  332. actshield/static/traces/__next.traces/__PAGE__.txt +27 -0
  333. actshield/static/traces.html +1 -0
  334. actshield/static/traces.txt +22 -0
  335. actshield/static/vercel.svg +1 -0
  336. actshield/static/window.svg +1 -0
  337. actshield/tasks/__init__.py +6 -0
  338. actshield/tasks/task.py +143 -0
  339. actshield/threatmodel/__init__.py +70 -0
  340. actshield/threatmodel/actors.py +210 -0
  341. actshield/threatmodel/analyzer.py +131 -0
  342. actshield/threatmodel/assets.py +167 -0
  343. actshield/threatmodel/boundaries.py +165 -0
  344. actshield/threatmodel/exporters.py +49 -0
  345. actshield/threatmodel/mitigations.py +209 -0
  346. actshield/threatmodel/models.py +80 -0
  347. actshield/threatmodel/report.py +341 -0
  348. actshield/threatmodel/scenarios.py +212 -0
  349. actshield/threatmodel/scoring.py +66 -0
  350. actshield/threatmodel/threats.py +387 -0
  351. actshield/tools/__init__.py +20 -0
  352. actshield/tools/interceptor.py +256 -0
  353. actshield/tools/tool.py +71 -0
  354. actshield/tracing/__init__.py +38 -0
  355. actshield/tracing/correlation.py +173 -0
  356. actshield/tracing/events.py +75 -0
  357. actshield/tracing/tracer.py +305 -0
  358. actshield/watch.py +129 -0
  359. actshield-0.9.0.dist-info/METADATA +57 -0
  360. actshield-0.9.0.dist-info/RECORD +366 -0
  361. actshield-0.9.0.dist-info/WHEEL +5 -0
  362. actshield-0.9.0.dist-info/entry_points.txt +4 -0
  363. actshield-0.9.0.dist-info/top_level.txt +2 -0
  364. agentguard/__init__.py +39 -0
  365. agentguard/__main__.py +8 -0
  366. agentguard/py.typed +1 -0
@@ -0,0 +1,20 @@
1
+ """Tools package."""
2
+
3
+ from actshield.tools.interceptor import protected_tool
4
+ from actshield.tools.tool import (
5
+ Resource,
6
+ SensitivityLevel,
7
+ ToolDefinition,
8
+ ToolRequest,
9
+ ToolResult,
10
+ )
11
+
12
+ __all__ = [
13
+ "Resource",
14
+ "SensitivityLevel",
15
+ "ToolDefinition",
16
+ "ToolRequest",
17
+ "ToolResult",
18
+ "protected_tool",
19
+ ]
20
+
@@ -0,0 +1,256 @@
1
+ """Tool execution interception and runtime policy enforcement wrapper."""
2
+
3
+ import functools
4
+ import inspect
5
+ import time
6
+ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
7
+
8
+ from actshield.context.context import Context
9
+ from actshield.context.provenance import ContextSource
10
+ from actshield.context.taint import TaintState
11
+ from actshield.decisions.decision import DecisionAction, SecurityDecision
12
+ from actshield.tools.tool import Resource, SensitivityLevel, ToolDefinition, ToolRequest, ToolResult
13
+ from actshield.tracing.correlation import generate_id, get_current_context
14
+ from actshield.tracing.events import EventType
15
+
16
+ if TYPE_CHECKING:
17
+ from actshield.client import ActShield
18
+
19
+
20
+ def protected_tool(
21
+ guard: "ActShield",
22
+ name: Optional[str] = None,
23
+ description: str = "",
24
+ sensitivity: SensitivityLevel = SensitivityLevel.MEDIUM,
25
+ required_capabilities: Optional[List[str]] = None,
26
+ classification: str = "internal",
27
+ target_resources: Optional[List[Resource]] = None,
28
+ metadata: Optional[Dict[str, Any]] = None,
29
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
30
+ """Decorator to register and wrap a tool function with ActShield security enforcement."""
31
+
32
+ def decorator(fn: Callable[..., Any]) -> Callable[..., Any]:
33
+ tool_name = name or fn.__name__
34
+ tool_desc = description or fn.__doc__ or ""
35
+ req_caps = required_capabilities or []
36
+ res_list = target_resources or []
37
+
38
+ tool_def = ToolDefinition(
39
+ tool_id=generate_id("tool"),
40
+ name=tool_name,
41
+ description=tool_desc,
42
+ sensitivity=sensitivity,
43
+ required_capabilities=req_caps,
44
+ classification=classification,
45
+ target_resources=res_list,
46
+ metadata=metadata or {},
47
+ )
48
+
49
+ guard.register_tool(tool_def)
50
+
51
+ if inspect.iscoroutinefunction(fn):
52
+ @functools.wraps(fn)
53
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
54
+ return await _execute_protected_tool_async(guard, tool_def, fn, *args, **kwargs)
55
+ async_wrapper.tool_def = tool_def # type: ignore
56
+ return async_wrapper
57
+ else:
58
+ @functools.wraps(fn)
59
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
60
+ return _execute_protected_tool_sync(guard, tool_def, fn, *args, **kwargs)
61
+ sync_wrapper.tool_def = tool_def # type: ignore
62
+ return sync_wrapper
63
+
64
+ return decorator
65
+
66
+
67
+ def _prepare_tool_request(
68
+ guard: "ActShield",
69
+ tool_def: ToolDefinition,
70
+ fn: Callable[..., Any],
71
+ *args: Any,
72
+ **kwargs: Any,
73
+ ) -> ToolRequest:
74
+ ctx = get_current_context()
75
+ agent_id = ctx.agent_id if ctx else None
76
+
77
+ # Map positional args to param names
78
+ sig = inspect.signature(fn)
79
+ bound_args = sig.bind_partial(*args, **kwargs)
80
+ bound_args.apply_defaults()
81
+ raw_arguments = dict(bound_args.arguments)
82
+
83
+ # Sanitize arguments for logging/event storage
84
+ safe_arguments = guard.config.sanitize(raw_arguments)
85
+
86
+ context_ids = []
87
+ if ctx and ctx.context_id:
88
+ context_ids.append(ctx.context_id)
89
+
90
+ target_res = tool_def.target_resources[0] if tool_def.target_resources else None
91
+
92
+ return ToolRequest(
93
+ request_id=generate_id("req"),
94
+ tool_id=tool_def.tool_id,
95
+ tool_name=tool_def.name,
96
+ agent_id=agent_id,
97
+ arguments=safe_arguments,
98
+ context_ids=context_ids,
99
+ target_resource=target_res,
100
+ )
101
+
102
+
103
+ def _execute_protected_tool_sync(
104
+ guard: "ActShield",
105
+ tool_def: ToolDefinition,
106
+ fn: Callable[..., Any],
107
+ *args: Any,
108
+ **kwargs: Any,
109
+ ) -> Any:
110
+ request = _prepare_tool_request(guard, tool_def, fn, *args, **kwargs)
111
+ ctx = get_current_context()
112
+
113
+ # Emit tool.requested
114
+ guard.emit_event(
115
+ event_type=EventType.TOOL_REQUESTED,
116
+ agent_id=request.agent_id,
117
+ payload={
118
+ "tool_id": tool_def.tool_id,
119
+ "tool_name": tool_def.name,
120
+ "arguments": request.arguments,
121
+ "sensitivity": tool_def.sensitivity.value,
122
+ }
123
+ )
124
+
125
+ # Emit resource.access_requested if applicable
126
+ if request.target_resource:
127
+ guard.emit_event(
128
+ event_type=EventType.RESOURCE_ACCESS_REQUESTED,
129
+ agent_id=request.agent_id,
130
+ payload={
131
+ "resource_id": request.target_resource.resource_id,
132
+ "resource_name": request.target_resource.name,
133
+ "sensitivity": request.target_resource.sensitivity.value,
134
+ }
135
+ )
136
+
137
+ # Evaluate security policy
138
+ decision: SecurityDecision = guard.evaluate_tool_invocation(tool_def, request)
139
+
140
+ if decision.is_blocked:
141
+ raise PermissionError(
142
+ f"ActShield Policy Enforced: Tool '{tool_def.name}' invocation BLOCKED. "
143
+ f"Reason: [{decision.reason_code}] {decision.explanation}"
144
+ )
145
+
146
+ start_t = time.perf_counter()
147
+ err_str = None
148
+ try:
149
+ raw_output = fn(*args, **kwargs)
150
+ elapsed_ms = (time.perf_counter() - start_t) * 1000
151
+ output_for_event = guard.config.sanitize(raw_output)
152
+
153
+ # Emit tool.invoked
154
+ guard.emit_event(
155
+ event_type=EventType.TOOL_INVOKED,
156
+ agent_id=request.agent_id,
157
+ payload={
158
+ "tool_id": tool_def.tool_id,
159
+ "tool_name": tool_def.name,
160
+ "success": True,
161
+ "execution_time_ms": elapsed_ms,
162
+ "output_summary": str(output_for_event)[:200],
163
+ }
164
+ )
165
+ return raw_output
166
+ except Exception as e:
167
+ elapsed_ms = (time.perf_counter() - start_t) * 1000
168
+ err_str = str(e)
169
+ guard.emit_event(
170
+ event_type=EventType.TOOL_INVOKED,
171
+ agent_id=request.agent_id,
172
+ payload={
173
+ "tool_id": tool_def.tool_id,
174
+ "tool_name": tool_def.name,
175
+ "success": False,
176
+ "error": err_str,
177
+ "execution_time_ms": elapsed_ms,
178
+ }
179
+ )
180
+ raise
181
+
182
+
183
+ async def _execute_protected_tool_async(
184
+ guard: "ActShield",
185
+ tool_def: ToolDefinition,
186
+ fn: Callable[..., Any],
187
+ *args: Any,
188
+ **kwargs: Any,
189
+ ) -> Any:
190
+ request = _prepare_tool_request(guard, tool_def, fn, *args, **kwargs)
191
+
192
+ # Emit tool.requested
193
+ guard.emit_event(
194
+ event_type=EventType.TOOL_REQUESTED,
195
+ agent_id=request.agent_id,
196
+ payload={
197
+ "tool_id": tool_def.tool_id,
198
+ "tool_name": tool_def.name,
199
+ "arguments": request.arguments,
200
+ "sensitivity": tool_def.sensitivity.value,
201
+ }
202
+ )
203
+
204
+ if request.target_resource:
205
+ guard.emit_event(
206
+ event_type=EventType.RESOURCE_ACCESS_REQUESTED,
207
+ agent_id=request.agent_id,
208
+ payload={
209
+ "resource_id": request.target_resource.resource_id,
210
+ "resource_name": request.target_resource.name,
211
+ "sensitivity": request.target_resource.sensitivity.value,
212
+ }
213
+ )
214
+
215
+ decision: SecurityDecision = guard.evaluate_tool_invocation(tool_def, request)
216
+
217
+ if decision.is_blocked:
218
+ raise PermissionError(
219
+ f"ActShield Policy Enforced: Tool '{tool_def.name}' invocation BLOCKED. "
220
+ f"Reason: [{decision.reason_code}] {decision.explanation}"
221
+ )
222
+
223
+ start_t = time.perf_counter()
224
+ try:
225
+ raw_output = await fn(*args, **kwargs)
226
+ elapsed_ms = (time.perf_counter() - start_t) * 1000
227
+ output_for_event = guard.config.sanitize(raw_output)
228
+
229
+ guard.emit_event(
230
+ event_type=EventType.TOOL_INVOKED,
231
+ agent_id=request.agent_id,
232
+ payload={
233
+ "tool_id": tool_def.tool_id,
234
+ "tool_name": tool_def.name,
235
+ "success": True,
236
+ "execution_time_ms": elapsed_ms,
237
+ "output_summary": str(output_for_event)[:200],
238
+ }
239
+ )
240
+ return raw_output
241
+ except Exception as e:
242
+ elapsed_ms = (time.perf_counter() - start_t) * 1000
243
+ guard.emit_event(
244
+ event_type=EventType.TOOL_INVOKED,
245
+ agent_id=request.agent_id,
246
+ payload={
247
+ "tool_id": tool_def.tool_id,
248
+ "tool_name": tool_def.name,
249
+ "success": False,
250
+ "error": str(e),
251
+ "execution_time_ms": elapsed_ms,
252
+ }
253
+ )
254
+ raise
255
+
256
+
@@ -0,0 +1,71 @@
1
+ """Tool and resource models for protected execution and access governance."""
2
+
3
+ from enum import Enum
4
+ from typing import Any, Dict, List, Optional
5
+ from pydantic import BaseModel, Field
6
+
7
+ from actshield.risk.models import RiskLevel
8
+ from actshield.tracing.correlation import generate_id
9
+
10
+
11
+ class SensitivityLevel(str, Enum):
12
+ """Sensitivity classification of tools and resources."""
13
+ LOW = "LOW"
14
+ MEDIUM = "MEDIUM"
15
+ HIGH = "HIGH"
16
+ CRITICAL = "CRITICAL"
17
+
18
+
19
+ class Resource(BaseModel):
20
+ """A protected asset, system, database, or API target accessed via tools."""
21
+ resource_id: str = Field(default_factory=lambda: generate_id("rsc"))
22
+ name: str = Field(..., description="Resource name (e.g. 'financial_database', 'user_pii_vault')")
23
+ resource_type: str = Field(default="api", description="Resource type ('database', 'filesystem', 'api', 'model')")
24
+ uri: Optional[str] = Field(default=None, description="URI or path locator")
25
+ sensitivity: SensitivityLevel = Field(default=SensitivityLevel.MEDIUM)
26
+ metadata: Dict[str, Any] = Field(default_factory=dict)
27
+
28
+
29
+ class ToolDefinition(BaseModel):
30
+ """Metadata and security properties defining an executable tool or MCP endpoint."""
31
+
32
+ tool_id: str = Field(default_factory=lambda: generate_id("tool"))
33
+ name: str = Field(..., description="Canonical tool name (e.g. 'execute_sql_query', 'fetch_webpage')")
34
+ description: str = Field(default="", description="Functional description of the tool")
35
+ sensitivity: SensitivityLevel = Field(default=SensitivityLevel.MEDIUM, description="Sensitivity classification")
36
+ required_capabilities: List[str] = Field(
37
+ default_factory=list,
38
+ description="List of capability names an agent must possess to invoke this tool"
39
+ )
40
+ classification: str = Field(
41
+ default="internal",
42
+ description="Origin classification: 'internal', 'external', 'mcp', 'sensitive'"
43
+ )
44
+ target_resources: List[Resource] = Field(
45
+ default_factory=list,
46
+ description="Underlying resources accessed by this tool"
47
+ )
48
+ metadata: Dict[str, Any] = Field(default_factory=dict)
49
+
50
+
51
+ class ToolRequest(BaseModel):
52
+ """A concrete invocation request targeting a tool."""
53
+
54
+ request_id: str = Field(default_factory=lambda: generate_id("req"))
55
+ tool_id: str = Field(default_factory=lambda: generate_id("tool"), description="Tool ID being invoked")
56
+ tool_name: str = Field(..., description="Tool name")
57
+ agent_id: Optional[str] = Field(default=None, description="Requesting agent ID")
58
+
59
+ arguments: Dict[str, Any] = Field(default_factory=dict, description="Tool invocation parameters")
60
+ context_ids: List[str] = Field(default_factory=list, description="IDs of context objects influencing this request")
61
+ target_resource: Optional[Resource] = Field(default=None, description="Specific target resource if applicable")
62
+
63
+
64
+ class ToolResult(BaseModel):
65
+ """The result or error produced by a tool execution."""
66
+
67
+ success: bool = Field(default=True)
68
+ output: Any = Field(default=None)
69
+ error: Optional[str] = Field(default=None)
70
+ execution_time_ms: float = Field(default=0.0)
71
+
@@ -0,0 +1,38 @@
1
+ """Tracing package."""
2
+
3
+ from actshield.tracing.correlation import (
4
+ CorrelationContext,
5
+ SpanScope,
6
+ generate_id,
7
+ get_current_agent_id,
8
+ get_current_context,
9
+ get_current_delegation_id,
10
+ get_current_span_id,
11
+ get_current_task_id,
12
+ get_current_trace_id,
13
+ reset_current_context,
14
+ set_current_context,
15
+ )
16
+ from actshield.tracing.events import EventType, SecurityEvent
17
+ from actshield.tracing.tracer import CausalEdge, CausalGraph, CausalNode, TraceManager
18
+
19
+ __all__ = [
20
+ "CorrelationContext",
21
+ "SpanScope",
22
+ "generate_id",
23
+ "get_current_context",
24
+ "set_current_context",
25
+ "reset_current_context",
26
+ "get_current_trace_id",
27
+ "get_current_span_id",
28
+ "get_current_agent_id",
29
+ "get_current_task_id",
30
+ "get_current_delegation_id",
31
+ "EventType",
32
+ "SecurityEvent",
33
+ "CausalNode",
34
+ "CausalEdge",
35
+ "CausalGraph",
36
+ "TraceManager",
37
+ ]
38
+
@@ -0,0 +1,173 @@
1
+ """Correlation context management for distributed and multi-agent tracing."""
2
+
3
+ import contextvars
4
+ import time
5
+ import uuid
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Dict, Optional
8
+
9
+
10
+ def generate_id(prefix: str = "") -> str:
11
+ """Generate a unique hex/uuid based ID with an optional prefix."""
12
+ unique_part = uuid.uuid4().hex[:16]
13
+ if prefix:
14
+ return f"{prefix}_{unique_part}"
15
+ return unique_part
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class CorrelationContext:
20
+ """Immutable correlation context representing current execution coordinates."""
21
+ trace_id: str
22
+ span_id: str
23
+ parent_span_id: Optional[str] = None
24
+ task_id: Optional[str] = None
25
+ agent_id: Optional[str] = None
26
+ parent_agent_id: Optional[str] = None
27
+ delegation_id: Optional[str] = None
28
+ parent_event_id: Optional[str] = None
29
+ context_id: Optional[str] = None
30
+ metadata: Dict[str, Any] = field(default_factory=dict)
31
+
32
+ def derive_child_span(
33
+ self,
34
+ span_id: Optional[str] = None,
35
+ agent_id: Optional[str] = None,
36
+ task_id: Optional[str] = None,
37
+ delegation_id: Optional[str] = None,
38
+ context_id: Optional[str] = None,
39
+ parent_event_id: Optional[str] = None,
40
+ extra_metadata: Optional[Dict[str, Any]] = None,
41
+ ) -> "CorrelationContext":
42
+ """Derive a child correlation context preserving the trace_id and updating span/agent relationships."""
43
+ new_span_id = span_id or generate_id("spn")
44
+ new_metadata = dict(self.metadata)
45
+ if extra_metadata:
46
+ new_metadata.update(extra_metadata)
47
+
48
+ return CorrelationContext(
49
+ trace_id=self.trace_id,
50
+ span_id=new_span_id,
51
+ parent_span_id=self.span_id,
52
+ task_id=task_id if task_id is not None else self.task_id,
53
+ agent_id=agent_id if agent_id is not None else self.agent_id,
54
+ parent_agent_id=self.agent_id if (agent_id is not None and agent_id != self.agent_id) else self.parent_agent_id,
55
+ delegation_id=delegation_id if delegation_id is not None else self.delegation_id,
56
+ parent_event_id=parent_event_id if parent_event_id is not None else self.parent_event_id,
57
+ context_id=context_id if context_id is not None else self.context_id,
58
+ metadata=new_metadata,
59
+ )
60
+
61
+
62
+ _current_correlation_ctx: contextvars.ContextVar[Optional[CorrelationContext]] = contextvars.ContextVar(
63
+ "agentguard_correlation_ctx", default=None
64
+ )
65
+
66
+
67
+ def get_current_context() -> Optional[CorrelationContext]:
68
+ """Retrieve the active correlation context for the current async/sync task."""
69
+ return _current_correlation_ctx.get()
70
+
71
+
72
+ def set_current_context(ctx: Optional[CorrelationContext]) -> contextvars.Token:
73
+ """Set the active correlation context and return the token to reset later."""
74
+ return _current_correlation_ctx.set(ctx)
75
+
76
+
77
+ def reset_current_context(token: contextvars.Token) -> None:
78
+ """Reset the correlation context using a saved token."""
79
+ _current_correlation_ctx.reset(token)
80
+
81
+
82
+ def get_current_trace_id() -> Optional[str]:
83
+ """Get the active trace ID if in an active trace scope."""
84
+ ctx = get_current_context()
85
+ return ctx.trace_id if ctx else None
86
+
87
+
88
+ def get_current_span_id() -> Optional[str]:
89
+ """Get the active span ID if in an active span scope."""
90
+ ctx = get_current_context()
91
+ return ctx.span_id if ctx else None
92
+
93
+
94
+ def get_current_agent_id() -> Optional[str]:
95
+ """Get the active agent ID."""
96
+ ctx = get_current_context()
97
+ return ctx.agent_id if ctx else None
98
+
99
+
100
+ def get_current_task_id() -> Optional[str]:
101
+ """Get the active task ID."""
102
+ ctx = get_current_context()
103
+ return ctx.task_id if ctx else None
104
+
105
+
106
+ def get_current_delegation_id() -> Optional[str]:
107
+ """Get the active delegation ID."""
108
+ ctx = get_current_context()
109
+ return ctx.delegation_id if ctx else None
110
+
111
+
112
+ class SpanScope:
113
+ """Context manager for establishing or nesting a correlation span."""
114
+
115
+ def __init__(
116
+ self,
117
+ name: str = "span",
118
+ trace_id: Optional[str] = None,
119
+ span_id: Optional[str] = None,
120
+ agent_id: Optional[str] = None,
121
+ task_id: Optional[str] = None,
122
+ delegation_id: Optional[str] = None,
123
+ context_id: Optional[str] = None,
124
+ metadata: Optional[Dict[str, Any]] = None,
125
+ ) -> None:
126
+ self.name = name
127
+ self._requested_trace_id = trace_id
128
+ self._requested_span_id = span_id
129
+ self._agent_id = agent_id
130
+ self._task_id = task_id
131
+ self._delegation_id = delegation_id
132
+ self._context_id = context_id
133
+ self._metadata = metadata or {}
134
+ self._token: Optional[contextvars.Token] = None
135
+ self.context: Optional[CorrelationContext] = None
136
+ self.start_time: float = 0.0
137
+
138
+ def __enter__(self) -> CorrelationContext:
139
+ self.start_time = time.time()
140
+ parent_ctx = get_current_context()
141
+ if parent_ctx is not None:
142
+ self.context = parent_ctx.derive_child_span(
143
+ span_id=self._requested_span_id,
144
+ agent_id=self._agent_id,
145
+ task_id=self._task_id,
146
+ delegation_id=self._delegation_id,
147
+ context_id=self._context_id,
148
+ extra_metadata=self._metadata,
149
+ )
150
+ else:
151
+ trace_id = self._requested_trace_id or generate_id("trc")
152
+ span_id = self._requested_span_id or generate_id("spn")
153
+ self.context = CorrelationContext(
154
+ trace_id=trace_id,
155
+ span_id=span_id,
156
+ task_id=self._task_id,
157
+ agent_id=self._agent_id,
158
+ delegation_id=self._delegation_id,
159
+ context_id=self._context_id,
160
+ metadata=self._metadata,
161
+ )
162
+ self._token = set_current_context(self.context)
163
+ return self.context
164
+
165
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
166
+ if self._token is not None:
167
+ reset_current_context(self._token)
168
+
169
+ async def __aenter__(self) -> CorrelationContext:
170
+ return self.__enter__()
171
+
172
+ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
173
+ self.__exit__(exc_type, exc_val, exc_tb)
@@ -0,0 +1,75 @@
1
+ """Strongly typed security events for multi-agent observability and enforcement."""
2
+
3
+ from datetime import datetime, timezone
4
+ from enum import Enum
5
+ from typing import Any, Dict, Optional
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+ from actshield.tracing.correlation import generate_id
9
+
10
+
11
+ class EventType(str, Enum):
12
+ """Enumeration of standard security-relevant event types."""
13
+ AGENT_CREATED = "agent.created"
14
+ AGENT_INVOKED = "agent.invoked"
15
+ TASK_STARTED = "task.started"
16
+ TASK_COMPLETED = "task.completed"
17
+ AGENT_DELEGATED = "agent.delegated"
18
+ CONTEXT_RECEIVED = "context.received"
19
+ CONTEXT_GENERATED = "context.generated"
20
+ CONTEXT_PROPAGATED = "context.propagated"
21
+ CONTEXT_SANITIZED = "context.sanitized"
22
+ TOOL_REQUESTED = "tool.requested"
23
+ TOOL_INVOKED = "tool.invoked"
24
+ TOOL_COMPLETED = "tool.completed"
25
+ RESOURCE_ACCESS_REQUESTED = "resource.access_requested"
26
+ SECURITY_EVALUATED = "security.evaluated"
27
+ SECURITY_DECISION = "security.decision"
28
+ HUMAN_APPROVAL_REQUESTED = "human.approval_requested"
29
+ HUMAN_APPROVAL_RECEIVED = "human.approval_received"
30
+ APPROVAL_REQUESTED = "approval.requested"
31
+ APPROVAL_GRANTED = "approval.granted"
32
+ APPROVAL_REJECTED = "approval.rejected"
33
+ INCIDENT_CREATED = "incident.created"
34
+ AI_ANALYSIS_COMPLETED = "ai.analysis_completed"
35
+ POSTURE_EVALUATED = "posture.evaluated"
36
+ INCIDENT_TRANSITIONED = "incident.transitioned"
37
+ RESPONSE_EXECUTED = "response.executed"
38
+ DRIFT_DETECTED = "drift.detected"
39
+ GATE_EVALUATED = "gate.evaluated"
40
+ CUSTOM_EVENT = "custom.event"
41
+ SECURITY_ALERT = "security.alert"
42
+
43
+
44
+
45
+
46
+ class SecurityEvent(BaseModel):
47
+ """Core security event model for distributed provenance and causal reconstruction."""
48
+
49
+ model_config = ConfigDict(arbitrary_types_allowed=True)
50
+
51
+ event_id: str = Field(default_factory=lambda: generate_id("evt"), description="Unique event ID")
52
+ event_type: EventType = Field(..., description="Categorical type of the security event")
53
+ timestamp: datetime = Field(
54
+ default_factory=lambda: datetime.now(timezone.utc),
55
+ description="UTC timestamp of when the event occurred"
56
+ )
57
+
58
+ # Correlation coordinates
59
+ trace_id: str = Field(..., description="Root trace ID for causal correlation")
60
+ span_id: str = Field(..., description="Span ID representing the current execution boundary")
61
+ parent_event_id: Optional[str] = Field(default=None, description="Immediate causal parent event ID")
62
+
63
+ # Agent coordinates
64
+ agent_id: Optional[str] = Field(default=None, description="Acting agent ID")
65
+ parent_agent_id: Optional[str] = Field(default=None, description="Delegating or parent agent ID")
66
+
67
+ # Execution coordinates
68
+ task_id: Optional[str] = Field(default=None, description="Active task ID")
69
+ delegation_id: Optional[str] = Field(default=None, description="Active delegation context ID")
70
+ context_id: Optional[str] = Field(default=None, description="Associated context or data lineage ID")
71
+
72
+ # Event data
73
+ payload: Dict[str, Any] = Field(default_factory=dict, description="Structured event payload")
74
+ metadata: Dict[str, Any] = Field(default_factory=dict, description="Custom metadata and tags")
75
+