admina-framework 0.11.0__tar.gz → 0.12.0__tar.gz

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 (172) hide show
  1. {admina_framework-0.11.0/admina_framework.egg-info → admina_framework-0.12.0}/PKG-INFO +14 -7
  2. {admina_framework-0.11.0 → admina_framework-0.12.0}/README.md +9 -2
  3. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/__init__.py +1 -1
  4. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/main.py +166 -0
  5. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/config.py +34 -3
  6. admina_framework-0.12.0/admina/domains/agent_security/coordination.py +822 -0
  7. admina_framework-0.12.0/admina/domains/agent_security/egress.py +574 -0
  8. admina_framework-0.12.0/admina/domains/agent_security/fingerprint.py +211 -0
  9. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/governance.py +43 -0
  10. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/engines/__init__.py +35 -1
  11. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/dashboard.py +7 -2
  12. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/gateway.py +5 -1
  13. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/integration.py +26 -1
  14. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/config.py +3 -1
  15. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/main.py +197 -4
  16. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/state.py +10 -0
  17. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/SHA256SUMS +1 -0
  18. admina_framework-0.12.0/admina/redteam/corpora/coordination.jsonl +7 -0
  19. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/governed_model.py +29 -0
  20. {admina_framework-0.11.0 → admina_framework-0.12.0/admina_framework.egg-info}/PKG-INFO +14 -7
  21. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/SOURCES.txt +17 -0
  22. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/requires.txt +4 -4
  23. {admina_framework-0.11.0 → admina_framework-0.12.0}/pyproject.toml +7 -7
  24. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_benchmark_14us.py +38 -0
  25. admina_framework-0.12.0/tests/test_coordination.py +1240 -0
  26. admina_framework-0.12.0/tests/test_coordination_cli.py +117 -0
  27. admina_framework-0.12.0/tests/test_coordination_corpus.py +147 -0
  28. admina_framework-0.12.0/tests/test_coordination_wiring.py +889 -0
  29. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_core_config.py +102 -0
  30. admina_framework-0.12.0/tests/test_egress_analyze.py +401 -0
  31. admina_framework-0.12.0/tests/test_egress_cli.py +257 -0
  32. admina_framework-0.12.0/tests/test_egress_pipeline.py +285 -0
  33. admina_framework-0.12.0/tests/test_egress_policy.py +137 -0
  34. admina_framework-0.12.0/tests/test_egress_surfaces.py +498 -0
  35. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_engines.py +132 -0
  36. admina_framework-0.12.0/tests/test_error_disclosure.py +72 -0
  37. admina_framework-0.12.0/tests/test_fingerprint.py +217 -0
  38. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_endpoints.py +1 -0
  39. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_guard_fail_mode.py +1 -0
  40. admina_framework-0.12.0/tests/test_proxy_governance_mode_config.py +56 -0
  41. admina_framework-0.12.0/tests/test_proxy_metrics_endpoint.py +143 -0
  42. {admina_framework-0.11.0 → admina_framework-0.12.0}/LICENSE +0 -0
  43. {admina_framework-0.11.0 → admina_framework-0.12.0}/NOTICE +0 -0
  44. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/__init__.py +0 -0
  45. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/commands/__init__.py +0 -0
  46. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/admina.yaml.j2 +0 -0
  47. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/docker-compose.yml.j2 +0 -0
  48. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/env.j2 +0 -0
  49. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/main.py.j2 +0 -0
  50. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin.py.j2 +0 -0
  51. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin_pyproject.toml.j2 +0 -0
  52. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin_readme.md.j2 +0 -0
  53. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/cli/templates/plugin_test.py.j2 +0 -0
  54. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/__init__.py +0 -0
  55. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/event_bus.py +0 -0
  56. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/secrets.py +0 -0
  57. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/core/types.py +0 -0
  58. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/__init__.py +0 -0
  59. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/static/heimdall.png +0 -0
  60. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/static/index.html +0 -0
  61. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
  62. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/__init__.py +0 -0
  63. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/agent_security/__init__.py +0 -0
  64. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/agent_security/firewall.py +0 -0
  65. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/agent_security/loop_breaker.py +0 -0
  66. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/__init__.py +0 -0
  67. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/llm_engine.py +0 -0
  68. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/rag.py +0 -0
  69. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/ai_infra/webui.py +0 -0
  70. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/__init__.py +0 -0
  71. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/cross_regulation.py +0 -0
  72. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/eu_ai_act.py +0 -0
  73. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/forensic.py +0 -0
  74. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/gdpr.py +0 -0
  75. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/nis2.py +0 -0
  76. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/oisg.py +0 -0
  77. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/compliance/otel.py +0 -0
  78. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/__init__.py +0 -0
  79. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/classification.py +0 -0
  80. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/pii.py +0 -0
  81. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/domains/data_sovereignty/residency.py +0 -0
  82. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/engines/presidio.py +0 -0
  83. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/__init__.py +0 -0
  84. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/_engines.py +0 -0
  85. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/cheshirecat/__init__.py +0 -0
  86. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
  87. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/crewai/__init__.py +0 -0
  88. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/crewai/callbacks.py +0 -0
  89. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/langchain/__init__.py +0 -0
  90. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/langchain/callbacks.py +0 -0
  91. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/n8n/__init__.py +0 -0
  92. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/integrations/openclaw/__init__.py +0 -0
  93. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/__init__.py +0 -0
  94. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/base.py +0 -0
  95. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/__init__.py +0 -0
  96. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/__init__.py +0 -0
  97. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/_streaming.py +0 -0
  98. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/anthropic.py +0 -0
  99. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/bedrock.py +0 -0
  100. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/gemini.py +0 -0
  101. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/mistral.py +0 -0
  102. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/ollama.py +0 -0
  103. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/openai.py +0 -0
  104. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/adapters/vllm.py +0 -0
  105. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/alerts/__init__.py +0 -0
  106. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/alerts/log.py +0 -0
  107. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/alerts/webhook.py +0 -0
  108. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/auth/__init__.py +0 -0
  109. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/auth/apikey.py +0 -0
  110. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/compliance/__init__.py +0 -0
  111. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/compliance/eu_ai_act.py +0 -0
  112. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/connectors/__init__.py +0 -0
  113. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/connectors/chromadb.py +0 -0
  114. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/connectors/filesystem.py +0 -0
  115. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/forensic/__init__.py +0 -0
  116. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/forensic/filesystem.py +0 -0
  117. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/guards/__init__.py +0 -0
  118. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
  119. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/pii/__init__.py +0 -0
  120. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/pii/spacy_regex.py +0 -0
  121. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/transports/__init__.py +0 -0
  122. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/transports/http_rest.py +0 -0
  123. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/builtin/transports/mcp.py +0 -0
  124. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/plugins/registry.py +0 -0
  125. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/__init__.py +0 -0
  126. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/api/__init__.py +0 -0
  127. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/engine_bridge.py +0 -0
  128. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/governance.py +0 -0
  129. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/proxy/multi_upstream.py +0 -0
  130. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/py.typed +0 -0
  131. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/__init__.py +0 -0
  132. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/baselines/baseline.json +0 -0
  133. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/injection.jsonl +0 -0
  134. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/loop.jsonl +0 -0
  135. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora/pii.jsonl +0 -0
  136. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/corpora.py +0 -0
  137. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/detectors.py +0 -0
  138. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/gate.py +0 -0
  139. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/metrics.py +0 -0
  140. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/report.py +0 -0
  141. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/redteam/runner.py +0 -0
  142. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/__init__.py +0 -0
  143. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/_compat.py +0 -0
  144. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/compliance_kit.py +0 -0
  145. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/errors.py +0 -0
  146. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/governed_agent.py +0 -0
  147. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/governed_data.py +0 -0
  148. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/retry.py +0 -0
  149. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina/sdk/streaming.py +0 -0
  150. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/dependency_links.txt +0 -0
  151. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/entry_points.txt +0 -0
  152. {admina_framework-0.11.0 → admina_framework-0.12.0}/admina_framework.egg-info/top_level.txt +0 -0
  153. {admina_framework-0.11.0 → admina_framework-0.12.0}/setup.cfg +0 -0
  154. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_domains.py +0 -0
  155. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_domains_governance.py +0 -0
  156. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_firewall_parity.py +0 -0
  157. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_forensic_blackbox.py +0 -0
  158. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_config.py +0 -0
  159. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_helpers.py +0 -0
  160. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_gateway_mount.py +0 -0
  161. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_governance_pipeline.py +0 -0
  162. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_guard_fail_mode.py +0 -0
  163. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_mcp_transport.py +0 -0
  164. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_presidio_engine.py +0 -0
  165. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_forensic_would_action.py +0 -0
  166. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_guard_fail_mode.py +0 -0
  167. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_mcp_response_pii.py +0 -0
  168. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_plugins.py +0 -0
  169. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_proxy_security.py +0 -0
  170. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_redteam_efficacy.py +0 -0
  171. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_redteam_lib.py +0 -0
  172. {admina_framework-0.11.0 → admina_framework-0.12.0}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: admina-framework
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: Admina — governed AI development framework
5
5
  Author-email: Stefano Noferi <info@admina.org>
6
6
  Maintainer-email: Stefano Noferi <info@admina.org>
@@ -30,7 +30,7 @@ License-File: NOTICE
30
30
  Requires-Dist: pyyaml<7,>=6.0
31
31
  Requires-Dist: click<9,>=8.1
32
32
  Requires-Dist: jinja2<4,>=3.1
33
- Requires-Dist: cryptography<50,>=41.0
33
+ Requires-Dist: cryptography<51,>=41.0
34
34
  Provides-Extra: proxy
35
35
  Requires-Dist: fastapi<1,>=0.104; extra == "proxy"
36
36
  Requires-Dist: uvicorn[standard]<1,>=0.24; extra == "proxy"
@@ -52,15 +52,15 @@ Requires-Dist: opentelemetry-sdk<2,>=1.20; extra == "telemetry"
52
52
  Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2,>=1.20; extra == "telemetry"
53
53
  Requires-Dist: opentelemetry-instrumentation-fastapi<1,>=0.40b0; extra == "telemetry"
54
54
  Provides-Extra: rust
55
- Requires-Dist: admina-core<0.12,>=0.9.3; extra == "rust"
55
+ Requires-Dist: admina-core<0.13,>=0.9.3; extra == "rust"
56
56
  Provides-Extra: full
57
57
  Requires-Dist: admina-framework[nlp,proxy,telemetry]; extra == "full"
58
58
  Provides-Extra: openai
59
- Requires-Dist: openai<3,>=1.0; extra == "openai"
59
+ Requires-Dist: openai<4,>=1.0; extra == "openai"
60
60
  Provides-Extra: ollama
61
61
  Requires-Dist: ollama<1,>=0.3; extra == "ollama"
62
62
  Provides-Extra: anthropic
63
- Requires-Dist: anthropic<1,>=0.39; extra == "anthropic"
63
+ Requires-Dist: anthropic<2,>=0.39; extra == "anthropic"
64
64
  Provides-Extra: mistral
65
65
  Requires-Dist: mistralai<3,>=1.0; extra == "mistral"
66
66
  Provides-Extra: gemini
@@ -331,16 +331,23 @@ from admina import GovernedModel, GovernedData, GovernedAgent, ComplianceKit
331
331
 
332
332
  | Interface | Builtin implementations |
333
333
  |-----------|------------------------|
334
- | Model Adapter | Ollama, OpenAI |
334
+ | Model Adapter | Ollama, OpenAI, Anthropic, Gemini, Mistral, Bedrock, vLLM |
335
335
  | Data Connector | ChromaDB, Filesystem |
336
336
  | Governance Domain | GuardrailsAI (toxic, jailbreak, bias, PII) |
337
337
  | Compliance Template | EU AI Act |
338
338
  | Transport Adapter | MCP, HTTP REST |
339
339
  | Forensic Store | Filesystem, S3-compatible (boto3 — AWS S3, MinIO, R2, …) |
340
340
  | Auth Provider | API Key |
341
- | PII Engine | spaCy + Regex |
341
+ | PII Engine | spaCy + Regex (default), Microsoft Presidio (`pip install admina-framework[presidio]`, `ADMINA_PII_ENGINE=presidio`) |
342
342
  | Alert Channel | Log, Webhook |
343
343
 
344
+ Model adapters lazy-import their provider SDK, so install the ones you
345
+ use: `pip install admina-framework[adapters]` for all of them, or a
346
+ single provider (`[openai]`, `[ollama]`, `[anthropic]`, `[mistral]`,
347
+ `[gemini]`, `[bedrock]`). vLLM has no extra of its own — it serves an
348
+ OpenAI-compatible API and subclasses the OpenAI adapter, so install
349
+ `[openai]` for it.
350
+
344
351
  ```bash
345
352
  admina plugin list # List registered plugins
346
353
  admina plugin install ./my-plugin # Install a custom plugin
@@ -254,16 +254,23 @@ from admina import GovernedModel, GovernedData, GovernedAgent, ComplianceKit
254
254
 
255
255
  | Interface | Builtin implementations |
256
256
  |-----------|------------------------|
257
- | Model Adapter | Ollama, OpenAI |
257
+ | Model Adapter | Ollama, OpenAI, Anthropic, Gemini, Mistral, Bedrock, vLLM |
258
258
  | Data Connector | ChromaDB, Filesystem |
259
259
  | Governance Domain | GuardrailsAI (toxic, jailbreak, bias, PII) |
260
260
  | Compliance Template | EU AI Act |
261
261
  | Transport Adapter | MCP, HTTP REST |
262
262
  | Forensic Store | Filesystem, S3-compatible (boto3 — AWS S3, MinIO, R2, …) |
263
263
  | Auth Provider | API Key |
264
- | PII Engine | spaCy + Regex |
264
+ | PII Engine | spaCy + Regex (default), Microsoft Presidio (`pip install admina-framework[presidio]`, `ADMINA_PII_ENGINE=presidio`) |
265
265
  | Alert Channel | Log, Webhook |
266
266
 
267
+ Model adapters lazy-import their provider SDK, so install the ones you
268
+ use: `pip install admina-framework[adapters]` for all of them, or a
269
+ single provider (`[openai]`, `[ollama]`, `[anthropic]`, `[mistral]`,
270
+ `[gemini]`, `[bedrock]`). vLLM has no extra of its own — it serves an
271
+ OpenAI-compatible API and subclasses the OpenAI adapter, so install
272
+ `[openai]` for it.
273
+
267
274
  ```bash
268
275
  admina plugin list # List registered plugins
269
276
  admina plugin install ./my-plugin # Install a custom plugin
@@ -23,7 +23,7 @@ from __future__ import annotations
23
23
 
24
24
  from admina.sdk import ComplianceKit, GovernedAgent, GovernedData, GovernedModel
25
25
 
26
- __version__ = "0.11.0"
26
+ __version__ = "0.12.0"
27
27
 
28
28
  __all__ = [
29
29
  "__version__",
@@ -20,6 +20,7 @@ Entry point: ``admina = "cli.main:app"`` in pyproject.toml.
20
20
  from __future__ import annotations
21
21
 
22
22
  import hashlib
23
+ import json
23
24
  import logging
24
25
  import os
25
26
  import shutil
@@ -1485,6 +1486,171 @@ def password_set(new_password: str) -> None:
1485
1486
  click.echo(" Restart services to apply: docker compose up --build -d\n")
1486
1487
 
1487
1488
 
1489
+ # ── admina egress commands ───────────────────────────────────
1490
+
1491
+
1492
+ @app.group()
1493
+ def egress() -> None:
1494
+ """Inspect and manage the egress policy."""
1495
+
1496
+
1497
+ @egress.command("suggest-allowlist")
1498
+ @click.option(
1499
+ "--since",
1500
+ type=click.IntRange(min=0),
1501
+ default=7,
1502
+ help="Only consider records from the last N days.",
1503
+ )
1504
+ @click.option(
1505
+ "--forensic-dir",
1506
+ default=None,
1507
+ help=(
1508
+ "Directory holding forensic records (the filesystem store's base_dir). "
1509
+ "Defaults to $FORENSIC_BASE_DIR, then .admina/forensic."
1510
+ ),
1511
+ )
1512
+ def suggest_allowlist(since: int, forensic_dir: str | None) -> None:
1513
+ """Print a candidate allowlist from destinations seen in observe mode."""
1514
+ # Same source `admina doctor` reads for the filesystem store; the flag
1515
+ # stays an explicit override.
1516
+ base = Path(forensic_dir or os.environ.get("FORENSIC_BASE_DIR") or ".admina/forensic")
1517
+ cutoff = time.time() - since * 86400
1518
+ seen: set[str] = set()
1519
+ for path in sorted(base.rglob("*.json")) if base.is_dir() else []:
1520
+ try:
1521
+ if path.stat().st_mtime < cutoff:
1522
+ continue
1523
+ record = json.loads(path.read_text())
1524
+ except (OSError, ValueError):
1525
+ continue
1526
+ # Handle both wrapped (ForensicBlackBox) and flat (test) record shapes
1527
+ if not isinstance(record, dict):
1528
+ continue
1529
+ event = record.get("event", record)
1530
+ if not isinstance(event, dict):
1531
+ continue
1532
+ check = event.get("checks") or {}
1533
+ if not isinstance(check, dict):
1534
+ continue
1535
+ egress = check.get("egress") or {}
1536
+ if not isinstance(egress, dict):
1537
+ continue
1538
+ destinations = egress.get("destinations") or []
1539
+ if not isinstance(destinations, list):
1540
+ continue
1541
+ for dest in destinations:
1542
+ if isinstance(dest, str) and dest:
1543
+ seen.add(dest)
1544
+
1545
+ if not seen:
1546
+ click.echo(f"No egress observations found in {base} for the last {since} day(s).")
1547
+ click.echo("Run with ADMINA_EGRESS_MODE=observe to collect them.")
1548
+ click.echo(
1549
+ "Observations are read back from forensic records, and FORENSIC_BACKEND "
1550
+ "defaults to 'memory' — set FORENSIC_BACKEND=filesystem and "
1551
+ "FORENSIC_BASE_DIR for anything to be written."
1552
+ )
1553
+ return
1554
+
1555
+ click.echo("# Candidate allowlist from observed traffic.")
1556
+ click.echo("# Review every entry before promoting it: observation records what")
1557
+ click.echo("# happened, not what should be allowed.")
1558
+ click.echo("domains:")
1559
+ click.echo(" agent_security:")
1560
+ click.echo(" egress:")
1561
+ click.echo(" allow:")
1562
+ for dest in sorted(seen):
1563
+ click.echo(f" - {dest}")
1564
+
1565
+
1566
+ def _quarantine_redis(url: str):
1567
+ """Open an async Redis client. Isolated so tests can substitute it."""
1568
+ import redis.asyncio as aioredis
1569
+
1570
+ return aioredis.from_url(url, decode_responses=True)
1571
+
1572
+
1573
+ @egress.group()
1574
+ def quarantine() -> None:
1575
+ """Inspect and clear quarantined destinations."""
1576
+
1577
+
1578
+ @quarantine.command("list")
1579
+ @click.option(
1580
+ "--redis-url",
1581
+ default=lambda: os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
1582
+ )
1583
+ def quarantine_list(redis_url: str) -> None:
1584
+ """Show destinations currently refused for payload-bearing calls."""
1585
+ import asyncio
1586
+ import time
1587
+
1588
+ from admina.domains.agent_security.coordination import QuarantineStore
1589
+
1590
+ async def _run():
1591
+ client = _quarantine_redis(redis_url)
1592
+ try:
1593
+ store = QuarantineStore(client, ttl_seconds=1)
1594
+ return await store.current_raising(time.time())
1595
+ finally:
1596
+ await client.close()
1597
+
1598
+ try:
1599
+ live = sorted(asyncio.run(_run()))
1600
+ except Exception as exc: # noqa: BLE001
1601
+ click.echo(f"Could not read quarantine set: {exc}")
1602
+ raise SystemExit(1)
1603
+
1604
+ if not live:
1605
+ click.echo("No destination is currently quarantined.")
1606
+ return
1607
+ click.echo("Quarantined for writes (reads are unaffected):")
1608
+ for destination in live:
1609
+ click.echo(f" - {destination}")
1610
+ click.echo("")
1611
+ click.echo(
1612
+ "Write-shaped calls to these are refused only under ADMINA_EGRESS_MODE=enforce; "
1613
+ "under the default observe mode they are recorded and allowed."
1614
+ )
1615
+
1616
+
1617
+ @quarantine.command("lift")
1618
+ @click.argument("destination")
1619
+ @click.option(
1620
+ "--redis-url",
1621
+ default=lambda: os.environ.get("REDIS_URL", "redis://localhost:6379/0"),
1622
+ )
1623
+ def quarantine_lift(destination: str, redis_url: str) -> None:
1624
+ """Clear one destination's quarantine."""
1625
+ import asyncio
1626
+
1627
+ from admina.domains.agent_security.coordination import QuarantineStore
1628
+
1629
+ # Destinations are stored as the egress analyser normalised them, which
1630
+ # lower-cases every host. An operator reading WIKI.CORP off a dashboard
1631
+ # would otherwise be told it was not quarantined.
1632
+ destination = destination.strip().lower()
1633
+
1634
+ async def _run():
1635
+ client = _quarantine_redis(redis_url)
1636
+ try:
1637
+ store = QuarantineStore(client, ttl_seconds=1)
1638
+ return await store.lift_raising(destination)
1639
+ finally:
1640
+ await client.close()
1641
+
1642
+ try:
1643
+ lifted = asyncio.run(_run())
1644
+ except Exception as exc: # noqa: BLE001
1645
+ click.echo(f"Could not reach quarantine store: {exc}")
1646
+ raise SystemExit(1)
1647
+
1648
+ if lifted:
1649
+ click.echo(f"Lifted quarantine on {destination}.")
1650
+ else:
1651
+ click.echo(f"{destination} was not quarantined.")
1652
+
1653
+
1488
1654
  @app.command()
1489
1655
  @click.option(
1490
1656
  "--output",
@@ -152,6 +152,20 @@ class LoopBreakerConfig:
152
152
  max_consecutive: int = 3
153
153
 
154
154
 
155
+ @dataclass
156
+ class EgressConfig:
157
+ """Destination-based egress control settings."""
158
+
159
+ enabled: bool = True
160
+ allow: list[str] = field(default_factory=list)
161
+ read_only_tools: list[str] = field(default_factory=list)
162
+ # Parsed now, consumed by the coordination detector.
163
+ coordination_declared: list[str] = field(default_factory=list)
164
+ fanin_window_seconds: int = 3600
165
+ fanin_min_agents: int = 5
166
+ quarantine_ttl_seconds: int = 86400
167
+
168
+
155
169
  @dataclass
156
170
  class AgentSecurityConfig:
157
171
  """Agent-security domain."""
@@ -160,6 +174,7 @@ class AgentSecurityConfig:
160
174
  proxy: ProxyConfig = field(default_factory=ProxyConfig)
161
175
  firewall: FirewallConfig = field(default_factory=FirewallConfig)
162
176
  loop_breaker: LoopBreakerConfig = field(default_factory=LoopBreakerConfig)
177
+ egress: EgressConfig = field(default_factory=EgressConfig)
163
178
 
164
179
 
165
180
  @dataclass
@@ -318,9 +333,16 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
318
333
 
319
334
  # agent_security
320
335
  as_raw = domains.get("agent_security", {})
321
- px_raw = as_raw.get("proxy", {})
322
- fw_raw = as_raw.get("firewall", {})
323
- lb_raw = as_raw.get("loop_breaker", {})
336
+ # ``or {}``: a section written with nothing under it ("egress:") parses
337
+ # as None, and the next .get() would raise. For egress that exception is
338
+ # swallowed by the factory's broad except and downgraded to an empty
339
+ # allowlist, which under enforce blocks every destination with no sign of
340
+ # why; for the other three it is an outright crash.
341
+ px_raw = as_raw.get("proxy") or {}
342
+ fw_raw = as_raw.get("firewall") or {}
343
+ lb_raw = as_raw.get("loop_breaker") or {}
344
+ eg_raw = as_raw.get("egress") or {}
345
+ eg_fanin = eg_raw.get("fanin", {}) or {}
324
346
  agent_sec = AgentSecurityConfig(
325
347
  enabled=as_raw.get("enabled", True),
326
348
  proxy=ProxyConfig(
@@ -339,6 +361,15 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
339
361
  similarity_threshold=lb_raw.get("similarity_threshold", 0.85),
340
362
  max_consecutive=lb_raw.get("max_consecutive", 3),
341
363
  ),
364
+ egress=EgressConfig(
365
+ enabled=eg_raw.get("enabled", True),
366
+ allow=list(eg_raw.get("allow") or []),
367
+ read_only_tools=list(eg_raw.get("read_only_tools") or []),
368
+ coordination_declared=list(eg_raw.get("coordination_declared") or []),
369
+ fanin_window_seconds=int(eg_fanin.get("window_seconds", 3600)),
370
+ fanin_min_agents=int(eg_fanin.get("min_agents", 5)),
371
+ quarantine_ttl_seconds=int(eg_raw.get("quarantine_ttl_seconds", 86400)),
372
+ ),
342
373
  )
343
374
 
344
375
  # compliance