admina-framework 0.9.5__tar.gz → 0.10.1__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 (134) hide show
  1. {admina_framework-0.9.5 → admina_framework-0.10.1}/NOTICE +1 -1
  2. {admina_framework-0.9.5/admina_framework.egg-info → admina_framework-0.10.1}/PKG-INFO +43 -6
  3. {admina_framework-0.9.5 → admina_framework-0.10.1}/README.md +22 -1
  4. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/__init__.py +1 -1
  5. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/main.py +67 -2
  6. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/templates/docker-compose.yml.j2 +3 -3
  7. admina_framework-0.10.1/admina/cli/templates/plugin.py.j2 +117 -0
  8. admina_framework-0.10.1/admina/cli/templates/plugin_pyproject.toml.j2 +21 -0
  9. admina_framework-0.10.1/admina/cli/templates/plugin_test.py.j2 +27 -0
  10. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/core/config.py +17 -2
  11. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/ai_infra/rag.py +49 -6
  12. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/eu_ai_act.py +26 -2
  13. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/forensic.py +77 -28
  14. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/oisg.py +17 -5
  15. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/data_sovereignty/pii.py +18 -0
  16. {admina_framework-0.9.5/admina/proxy → admina_framework-0.10.1/admina/domains}/governance.py +81 -11
  17. admina_framework-0.10.1/admina/engines/__init__.py +431 -0
  18. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/_engines.py +12 -9
  19. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/base.py +16 -4
  20. admina_framework-0.10.1/admina/plugins/builtin/adapters/anthropic.py +142 -0
  21. admina_framework-0.10.1/admina/plugins/builtin/adapters/bedrock.py +157 -0
  22. admina_framework-0.10.1/admina/plugins/builtin/adapters/gemini.py +137 -0
  23. admina_framework-0.10.1/admina/plugins/builtin/adapters/mistral.py +143 -0
  24. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/adapters/ollama.py +6 -6
  25. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/adapters/openai.py +6 -6
  26. admina_framework-0.10.1/admina/plugins/builtin/adapters/vllm.py +85 -0
  27. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/auth/apikey.py +59 -17
  28. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/compliance/eu_ai_act.py +2 -0
  29. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/connectors/chromadb.py +2 -5
  30. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/connectors/filesystem.py +2 -5
  31. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/forensic/filesystem.py +94 -26
  32. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/pii/spacy_regex.py +30 -3
  33. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/registry.py +19 -16
  34. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/api/dashboard.py +28 -13
  35. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/api/integration.py +89 -39
  36. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/config.py +4 -0
  37. admina_framework-0.10.1/admina/proxy/engine_bridge.py +44 -0
  38. admina_framework-0.10.1/admina/proxy/governance.py +37 -0
  39. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/main.py +190 -65
  40. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/sdk/__init__.py +6 -0
  41. admina_framework-0.10.1/admina/sdk/errors.py +27 -0
  42. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/sdk/governed_agent.py +47 -103
  43. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/sdk/governed_data.py +38 -40
  44. admina_framework-0.10.1/admina/sdk/governed_model.py +325 -0
  45. admina_framework-0.10.1/admina/sdk/retry.py +127 -0
  46. {admina_framework-0.9.5 → admina_framework-0.10.1/admina_framework.egg-info}/PKG-INFO +43 -6
  47. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina_framework.egg-info/SOURCES.txt +13 -0
  48. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina_framework.egg-info/requires.txt +28 -4
  49. {admina_framework-0.9.5 → admina_framework-0.10.1}/pyproject.toml +17 -6
  50. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_core_config.py +56 -9
  51. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_domains.py +17 -2
  52. admina_framework-0.10.1/tests/test_domains_governance.py +317 -0
  53. admina_framework-0.10.1/tests/test_engines.py +309 -0
  54. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_forensic_blackbox.py +161 -0
  55. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_governance_pipeline.py +9 -3
  56. admina_framework-0.10.1/tests/test_proxy_mcp_response_pii.py +149 -0
  57. admina_framework-0.10.1/tests/test_proxy_plugins.py +93 -0
  58. admina_framework-0.10.1/tests/test_proxy_security.py +462 -0
  59. admina_framework-0.9.5/admina/cli/templates/plugin.py.j2 +0 -145
  60. admina_framework-0.9.5/admina/cli/templates/plugin_pyproject.toml.j2 +0 -15
  61. admina_framework-0.9.5/admina/cli/templates/plugin_test.py.j2 +0 -48
  62. admina_framework-0.9.5/admina/proxy/engine_bridge.py +0 -251
  63. admina_framework-0.9.5/admina/sdk/governed_model.py +0 -243
  64. admina_framework-0.9.5/tests/test_proxy_security.py +0 -210
  65. {admina_framework-0.9.5 → admina_framework-0.10.1}/LICENSE +0 -0
  66. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/__init__.py +0 -0
  67. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/commands/__init__.py +0 -0
  68. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/templates/admina.yaml.j2 +0 -0
  69. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/templates/env.j2 +0 -0
  70. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/templates/main.py.j2 +0 -0
  71. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/cli/templates/plugin_readme.md.j2 +0 -0
  72. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/core/__init__.py +0 -0
  73. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/core/event_bus.py +0 -0
  74. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/core/secrets.py +0 -0
  75. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/core/types.py +0 -0
  76. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/dashboard/__init__.py +0 -0
  77. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/dashboard/static/heimdall.png +0 -0
  78. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/dashboard/static/index.html +0 -0
  79. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
  80. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/__init__.py +0 -0
  81. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/agent_security/__init__.py +0 -0
  82. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/agent_security/firewall.py +0 -0
  83. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/agent_security/loop_breaker.py +0 -0
  84. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/ai_infra/__init__.py +0 -0
  85. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/ai_infra/llm_engine.py +0 -0
  86. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/ai_infra/webui.py +0 -0
  87. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/__init__.py +0 -0
  88. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/cross_regulation.py +0 -0
  89. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/gdpr.py +0 -0
  90. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/nis2.py +0 -0
  91. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/compliance/otel.py +0 -0
  92. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/data_sovereignty/__init__.py +0 -0
  93. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/data_sovereignty/classification.py +0 -0
  94. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/domains/data_sovereignty/residency.py +0 -0
  95. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/__init__.py +0 -0
  96. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/cheshirecat/__init__.py +0 -0
  97. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
  98. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/crewai/__init__.py +0 -0
  99. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/crewai/callbacks.py +0 -0
  100. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/langchain/__init__.py +0 -0
  101. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/langchain/callbacks.py +0 -0
  102. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/n8n/__init__.py +0 -0
  103. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/integrations/openclaw/__init__.py +0 -0
  104. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/__init__.py +0 -0
  105. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/__init__.py +0 -0
  106. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/adapters/__init__.py +0 -0
  107. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/alerts/__init__.py +0 -0
  108. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/alerts/log.py +0 -0
  109. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/alerts/webhook.py +0 -0
  110. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/auth/__init__.py +0 -0
  111. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/compliance/__init__.py +0 -0
  112. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/connectors/__init__.py +0 -0
  113. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/forensic/__init__.py +0 -0
  114. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/guards/__init__.py +0 -0
  115. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
  116. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/pii/__init__.py +0 -0
  117. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/transports/__init__.py +0 -0
  118. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/transports/http_rest.py +0 -0
  119. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/plugins/builtin/transports/mcp.py +0 -0
  120. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/__init__.py +0 -0
  121. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/api/__init__.py +0 -0
  122. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/multi_upstream.py +0 -0
  123. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/proxy/state.py +0 -0
  124. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/py.typed +0 -0
  125. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/sdk/_compat.py +0 -0
  126. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina/sdk/compliance_kit.py +0 -0
  127. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina_framework.egg-info/dependency_links.txt +0 -0
  128. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina_framework.egg-info/entry_points.txt +0 -0
  129. {admina_framework-0.9.5 → admina_framework-0.10.1}/admina_framework.egg-info/top_level.txt +0 -0
  130. {admina_framework-0.9.5 → admina_framework-0.10.1}/setup.cfg +0 -0
  131. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_benchmark_14us.py +0 -0
  132. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_firewall_parity.py +0 -0
  133. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_mcp_transport.py +0 -0
  134. {admina_framework-0.9.5 → admina_framework-0.10.1}/tests/test_version.py +0 -0
@@ -9,7 +9,7 @@ Source: https://github.com/admina-org/admina
9
9
 
10
10
  ─── Bundled assets ─────────────────────────────────────────────────
11
11
 
12
- * dashboard/heimdall.png
12
+ * admina/dashboard/static/heimdall.png
13
13
  "Heimdall — the Governance Owl"
14
14
  Copyright © 2025–2026 Stefano Noferi & Admina contributors
15
15
  Licensed under the Apache License, Version 2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: admina-framework
3
- Version: 0.9.5
3
+ Version: 0.10.1
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<48,>=41.0
33
+ Requires-Dist: cryptography<50,>=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"
@@ -38,9 +38,9 @@ Requires-Dist: httpx<1,>=0.25; extra == "proxy"
38
38
  Requires-Dist: pydantic<3,>=2.5; extra == "proxy"
39
39
  Requires-Dist: pydantic-settings<3,>=2.3; extra == "proxy"
40
40
  Requires-Dist: python-dotenv<2,>=1.0; extra == "proxy"
41
- Requires-Dist: redis<6,>=5.0; extra == "proxy"
41
+ Requires-Dist: redis<9,>=5.0; extra == "proxy"
42
42
  Requires-Dist: boto3<2,>=1.34; extra == "proxy"
43
- Requires-Dist: clickhouse-connect<1,>=0.7; extra == "proxy"
43
+ Requires-Dist: clickhouse-connect<2,>=0.7; extra == "proxy"
44
44
  Requires-Dist: typer<1,>=0.9; extra == "proxy"
45
45
  Requires-Dist: numpy<2,>=1.24; extra == "proxy"
46
46
  Requires-Dist: scikit-learn<2,>=1.3; extra == "proxy"
@@ -52,9 +52,25 @@ 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.10,>=0.9.3; extra == "rust"
55
+ Requires-Dist: admina-core<0.11,>=0.9.3; extra == "rust"
56
56
  Provides-Extra: full
57
57
  Requires-Dist: admina-framework[nlp,proxy,telemetry]; extra == "full"
58
+ Provides-Extra: openai
59
+ Requires-Dist: openai<3,>=1.0; extra == "openai"
60
+ Provides-Extra: ollama
61
+ Requires-Dist: ollama<1,>=0.3; extra == "ollama"
62
+ Provides-Extra: anthropic
63
+ Requires-Dist: anthropic<1,>=0.39; extra == "anthropic"
64
+ Provides-Extra: mistral
65
+ Requires-Dist: mistralai<3,>=1.0; extra == "mistral"
66
+ Provides-Extra: gemini
67
+ Requires-Dist: google-genai<3,>=1.0; extra == "gemini"
68
+ Provides-Extra: bedrock
69
+ Requires-Dist: boto3<2,>=1.34; extra == "bedrock"
70
+ Provides-Extra: adapters
71
+ Requires-Dist: admina-framework[anthropic,bedrock,gemini,mistral,ollama,openai]; extra == "adapters"
72
+ Provides-Extra: all
73
+ Requires-Dist: admina-framework[adapters,nlp,proxy,telemetry]; extra == "all"
58
74
  Dynamic: license-file
59
75
 
60
76
  <!--
@@ -186,6 +202,21 @@ pip install "admina-framework[proxy]"
186
202
  pip install "admina-framework[full]"
187
203
  python -m spacy download en_core_web_sm # for [full] only
188
204
 
205
+ # Model-adapter provider SDKs (per-provider extras):
206
+ pip install "admina-framework[openai]" # openai>=1.0
207
+ pip install "admina-framework[ollama]" # ollama>=0.3
208
+ pip install "admina-framework[anthropic]" # anthropic>=0.39
209
+ pip install "admina-framework[mistral]" # mistralai>=1.0
210
+ pip install "admina-framework[gemini]" # google-genai>=1.0
211
+ pip install "admina-framework[bedrock]" # boto3>=1.34 (AWS Bedrock)
212
+
213
+ # All provider SDKs at once:
214
+ pip install "admina-framework[adapters]"
215
+
216
+ # Everything (proxy + NLP + telemetry + all adapters):
217
+ pip install "admina-framework[all]"
218
+ python -m spacy download en_core_web_sm # for [all] only
219
+
189
220
  # Optional: Rust-accelerated engine (auto-detected at runtime).
190
221
  # Opt-in extra — pulls in the admina-core wheel from PyPI.
191
222
  pip install "admina-framework[rust]"
@@ -224,6 +255,12 @@ pip install -e ".[proxy]"
224
255
  # Everything (proxy + NLP + telemetry)
225
256
  pip install -e ".[full]"
226
257
 
258
+ # All model-adapter SDKs
259
+ pip install -e ".[adapters]"
260
+
261
+ # Everything (proxy + NLP + telemetry + all adapters)
262
+ pip install -e ".[all]"
263
+
227
264
  # CLI workflow
228
265
  admina init my-project # Scaffold a governed AI project
229
266
  cd my-project # admina dev runs from the project directory
@@ -258,7 +295,7 @@ flowchart LR
258
295
  class P pipe;
259
296
  ```
260
297
 
261
- Pipeline (identical in both modes): `PII redaction → firewall → loop-breaker → audit → forensic chain (SHA-256) → OTEL`
298
+ Pipeline (identical in both modes): `loop-breaker → firewall → PII redaction → guards → audit → forensic chain (SHA-256) → OTEL`
262
299
 
263
300
  ## The 4 Governance Domains
264
301
 
@@ -127,6 +127,21 @@ pip install "admina-framework[proxy]"
127
127
  pip install "admina-framework[full]"
128
128
  python -m spacy download en_core_web_sm # for [full] only
129
129
 
130
+ # Model-adapter provider SDKs (per-provider extras):
131
+ pip install "admina-framework[openai]" # openai>=1.0
132
+ pip install "admina-framework[ollama]" # ollama>=0.3
133
+ pip install "admina-framework[anthropic]" # anthropic>=0.39
134
+ pip install "admina-framework[mistral]" # mistralai>=1.0
135
+ pip install "admina-framework[gemini]" # google-genai>=1.0
136
+ pip install "admina-framework[bedrock]" # boto3>=1.34 (AWS Bedrock)
137
+
138
+ # All provider SDKs at once:
139
+ pip install "admina-framework[adapters]"
140
+
141
+ # Everything (proxy + NLP + telemetry + all adapters):
142
+ pip install "admina-framework[all]"
143
+ python -m spacy download en_core_web_sm # for [all] only
144
+
130
145
  # Optional: Rust-accelerated engine (auto-detected at runtime).
131
146
  # Opt-in extra — pulls in the admina-core wheel from PyPI.
132
147
  pip install "admina-framework[rust]"
@@ -165,6 +180,12 @@ pip install -e ".[proxy]"
165
180
  # Everything (proxy + NLP + telemetry)
166
181
  pip install -e ".[full]"
167
182
 
183
+ # All model-adapter SDKs
184
+ pip install -e ".[adapters]"
185
+
186
+ # Everything (proxy + NLP + telemetry + all adapters)
187
+ pip install -e ".[all]"
188
+
168
189
  # CLI workflow
169
190
  admina init my-project # Scaffold a governed AI project
170
191
  cd my-project # admina dev runs from the project directory
@@ -199,7 +220,7 @@ flowchart LR
199
220
  class P pipe;
200
221
  ```
201
222
 
202
- Pipeline (identical in both modes): `PII redaction → firewall → loop-breaker → audit → forensic chain (SHA-256) → OTEL`
223
+ Pipeline (identical in both modes): `loop-breaker → firewall → PII redaction → guards → audit → forensic chain (SHA-256) → OTEL`
203
224
 
204
225
  ## The 4 Governance Domains
205
226
 
@@ -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.9.5"
26
+ __version__ = "0.10.1"
27
27
 
28
28
  __all__ = [
29
29
  "__version__",
@@ -38,6 +38,8 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
38
38
  from admina import __version__
39
39
  from admina.core.secrets import SecretVault, validate_password
40
40
 
41
+ logger = logging.getLogger(__name__)
42
+
41
43
  _TEMPLATES_DIR = Path(__file__).parent / "templates"
42
44
 
43
45
  # Domains the user can toggle on/off during ``admina init``.
@@ -166,6 +168,7 @@ def _scaffold_project(
166
168
  context: dict[str, object] = {
167
169
  "project_name": project_name,
168
170
  "domains": {d: d in domains for d in AVAILABLE_DOMAINS},
171
+ "admina_version": __version__,
169
172
  }
170
173
 
171
174
  # admina.yaml
@@ -398,6 +401,7 @@ def _maybe_regenerate_compose(project_dir: Path, data: dict[str, object]) -> boo
398
401
  "project_name": project_name,
399
402
  "domains": {d: d in domains for d in AVAILABLE_DOMAINS},
400
403
  "with_llm": True, # init scaffolds the full template; admina dev gates at runtime
404
+ "admina_version": __version__,
401
405
  }
402
406
  _generate_file(env, "docker-compose.yml.j2", project_dir / "docker-compose.yml", context)
403
407
  hash_file.write_text(current_hash)
@@ -686,6 +690,7 @@ def _run_compose(
686
690
  "project_name": project_name,
687
691
  "domains": {d: d in domains for d in AVAILABLE_DOMAINS},
688
692
  "with_llm": with_llm,
693
+ "admina_version": __version__,
689
694
  }
690
695
  _generate_file(env_j, "docker-compose.yml.j2", project_dir / "docker-compose.yml", context)
691
696
  hash_file.write_text(current_hash)
@@ -885,12 +890,23 @@ def _pip_install(package: str) -> subprocess.CompletedProcess[str]:
885
890
  )
886
891
 
887
892
 
893
+ def _config_extra_modules() -> list[str]:
894
+ """Module paths from admina.yaml ``plugins:`` — empty if no config."""
895
+ try:
896
+ from admina.core.config import load_config
897
+
898
+ return list(load_config().plugins)
899
+ except (ImportError, ValueError, OSError) as exc:
900
+ logger.debug("Could not read plugins from admina.yaml: %s", exc)
901
+ return []
902
+
903
+
888
904
  def _discover_and_list_plugins() -> dict[str, dict[str, type]]:
889
905
  """Run plugin discovery and return all registered plugins by type."""
890
906
  from admina.plugins.registry import PluginRegistry
891
907
 
892
908
  registry = PluginRegistry()
893
- registry.discover()
909
+ registry.discover(extra_modules=_config_extra_modules() or None)
894
910
  return registry.list_all()
895
911
 
896
912
 
@@ -915,6 +931,9 @@ def _scaffold_plugin(
915
931
  "base_class": base_class,
916
932
  "class_name": class_name,
917
933
  "name_property": name_prop,
934
+ "needs_any": plugin_type
935
+ in {"model_adapter", "data_connector", "transport_adapter", "auth_provider"},
936
+ "admina_version": __version__,
918
937
  }
919
938
 
920
939
  created: list[str] = []
@@ -1215,7 +1234,7 @@ def doctor() -> None:
1215
1234
  from admina.plugins.registry import PluginRegistry
1216
1235
 
1217
1236
  reg = PluginRegistry()
1218
- count = reg.discover()
1237
+ count = reg.discover(extra_modules=_config_extra_modules() or None)
1219
1238
  click.echo(f" Discovered {ok_mark} {count} plugins")
1220
1239
  except Exception as exc:
1221
1240
  import traceback
@@ -1327,6 +1346,52 @@ def doctor() -> None:
1327
1346
  " proxy /health -- not reachable (stack down or different port)"
1328
1347
  )
1329
1348
 
1349
+ # ── Forensic hash-chain integrity ───────────────────────
1350
+ click.echo("\n Forensic chain:")
1351
+ forensic_backend = env.get("FORENSIC_BACKEND", os.environ.get("FORENSIC_BACKEND", "memory"))
1352
+ if forensic_backend in ("filesystem", "s3"):
1353
+ try:
1354
+ import asyncio as _asyncio
1355
+
1356
+ from admina.domains.compliance.forensic import ForensicBlackBox
1357
+
1358
+ if forensic_backend == "filesystem":
1359
+ base_dir = env.get("FORENSIC_BASE_DIR", os.environ.get("FORENSIC_BASE_DIR", ""))
1360
+ fbox = ForensicBlackBox(filesystem_dir=base_dir if base_dir else None)
1361
+ else:
1362
+ # s3 — needs boto3; construct without credentials (probe only)
1363
+ try:
1364
+ import boto3 as _boto3
1365
+ except ImportError:
1366
+ raise ImportError("boto3 not installed")
1367
+ s3_endpoint = env.get(
1368
+ "FORENSIC_S3_ENDPOINT", os.environ.get("FORENSIC_S3_ENDPOINT", "")
1369
+ )
1370
+ s3_bucket = env.get(
1371
+ "FORENSIC_S3_BUCKET", os.environ.get("FORENSIC_S3_BUCKET", "admina-forensic")
1372
+ )
1373
+ s3_kwargs: dict = {}
1374
+ if s3_endpoint:
1375
+ s3_kwargs["endpoint_url"] = s3_endpoint
1376
+ client = _boto3.client("s3", **s3_kwargs)
1377
+ fbox = ForensicBlackBox(boto3_client=client, bucket=s3_bucket)
1378
+
1379
+ chain = _asyncio.run(fbox.verify_chain())
1380
+ n = chain.get("records", 0)
1381
+ if chain.get("valid"):
1382
+ click.echo(f" {forensic_backend:20s} {ok_mark} {n} records, valid")
1383
+ else:
1384
+ click.echo(
1385
+ f" {forensic_backend:20s} {fail_mark} chain verification FAILED ({n} records)"
1386
+ )
1387
+ issues.append("Forensic hash-chain integrity check failed")
1388
+ except ImportError as _exc:
1389
+ click.echo(f" {forensic_backend:20s} -- skipped ({_exc})")
1390
+ except Exception as _exc:
1391
+ click.echo(f" {forensic_backend:20s} -- skipped (error: {_exc})")
1392
+ else:
1393
+ click.echo(f" {'not configured':20s} -- memory backend (no durable chain to verify)")
1394
+
1330
1395
  # ── Summary ──────────────────────────────────────────────
1331
1396
  click.echo("\n" + "=" * 55)
1332
1397
  if not issues:
@@ -7,9 +7,9 @@ services:
7
7
  # ── Core: Governance Proxy ──────────────────────────────
8
8
  {% if domains.agent_security %}
9
9
  proxy:
10
- # Pinned to v0.9.0 — change to :latest to track the rolling tag.
10
+ # Pinned to v{{ admina_version }} — change to :latest to track the rolling tag.
11
11
  # Images are published by .github/workflows/release-docker.yml on tag push.
12
- image: ghcr.io/admina-org/admina-proxy:0.9.0
12
+ image: ghcr.io/admina-org/admina-proxy:{{ admina_version }}
13
13
  container_name: {{ project_name }}-proxy
14
14
  ports:
15
15
  - "8080:8080"
@@ -40,7 +40,7 @@ services:
40
40
 
41
41
  # ── Dashboard ───────────────────────────────────────────
42
42
  dashboard:
43
- image: ghcr.io/admina-org/admina-dashboard:0.9.0
43
+ image: ghcr.io/admina-org/admina-dashboard:{{ admina_version }}
44
44
  container_name: {{ project_name }}-dashboard
45
45
  ports:
46
46
  - "3000:80"
@@ -0,0 +1,117 @@
1
+ """{{ plugin_name }} — Admina {{ plugin_type }} plugin."""
2
+ from __future__ import annotations
3
+
4
+ {% if needs_any %}from typing import Any
5
+
6
+ {% endif %}from admina.plugins.base import {{ base_class }}
7
+
8
+
9
+ class {{ class_name }}({{ base_class }}):
10
+ """{{ plugin_type.replace('_', ' ').title() }} plugin: {{ plugin_name }}.
11
+
12
+ TODO: Implement the required methods below.
13
+ """
14
+
15
+ name = "{{ plugin_name }}"
16
+ {%- if name_property == "framework_name" %}
17
+ framework_name = "{{ plugin_name }}"
18
+ {%- elif name_property == "protocol_name" %}
19
+ protocol_name = "{{ plugin_name }}"
20
+ {%- elif name_property == "store_name" %}
21
+ store_name = "{{ plugin_name }}"
22
+ {%- elif name_property == "provider_name" %}
23
+ provider_name = "{{ plugin_name }}"
24
+ {%- elif name_property == "channel_name" %}
25
+ channel_name = "{{ plugin_name }}"
26
+ {%- elif name_property == "supported_languages" %}
27
+ supported_languages = ["en"]
28
+ {%- endif %}
29
+ # Optional: accept per-plugin config from admina.yaml `plugin_config:`
30
+ # def __init__(self, config: dict | None = None) -> None:
31
+ # self._config = config or {}
32
+ {% if plugin_type == "model_adapter" %}
33
+ async def send(
34
+ self,
35
+ prompt: str,
36
+ context: Any = None,
37
+ **kwargs: Any,
38
+ ) -> dict[str, Any]:
39
+ """Send a prompt to the model and return the response.
40
+
41
+ Returns:
42
+ Dict with at least ``text`` and ``metadata`` keys.
43
+ """
44
+ raise NotImplementedError("TODO: implement send()")
45
+
46
+ def supports_model(self, model_name: str) -> bool:
47
+ """Return True if this adapter supports the given model."""
48
+ return True
49
+ {%- elif plugin_type == "data_connector" %}
50
+ async def ingest(self, source: Any, **kwargs: Any) -> dict[str, Any]:
51
+ """Ingest documents from *source* into the data store."""
52
+ raise NotImplementedError("TODO: implement ingest()")
53
+
54
+ async def query(self, query: str, **kwargs: Any) -> list[dict[str, Any]]:
55
+ """Query the data store and return ranked results."""
56
+ raise NotImplementedError("TODO: implement query()")
57
+ {%- elif plugin_type == "governance_guard" %}
58
+ async def inspect_request(self, request: dict) -> dict:
59
+ """Inspect an inbound request."""
60
+ return {"action": "ALLOW", "risk_level": "LOW", "details": ""}
61
+
62
+ async def inspect_response(self, response: dict) -> dict:
63
+ """Inspect an outbound response."""
64
+ return {"action": "ALLOW", "risk_level": "LOW", "details": ""}
65
+ {%- elif plugin_type == "compliance_template" %}
66
+ def get_requirements(self) -> list[dict]:
67
+ """Return the list of requirements for this framework."""
68
+ raise NotImplementedError("TODO: implement get_requirements()")
69
+
70
+ def evaluate(self, governance_state: dict) -> dict:
71
+ """Evaluate governance state against the requirements."""
72
+ raise NotImplementedError("TODO: implement evaluate()")
73
+ {%- elif plugin_type == "transport_adapter" %}
74
+ async def parse_request(self, raw_request: Any) -> Any:
75
+ """Parse a protocol-specific request into a GovernanceRequest."""
76
+ raise NotImplementedError("TODO: implement parse_request()")
77
+
78
+ async def format_response(self, gov_response: Any, original: Any) -> Any:
79
+ """Format a GovernanceResponse into the protocol wire format."""
80
+ raise NotImplementedError("TODO: implement format_response()")
81
+
82
+ def register_routes(self, app: Any) -> None:
83
+ """Register protocol-specific HTTP routes on the FastAPI app."""
84
+ raise NotImplementedError("TODO: implement register_routes()")
85
+ {%- elif plugin_type == "forensic_store" %}
86
+ async def append(self, record: dict) -> str:
87
+ """Persist a governance record; return a storage identifier."""
88
+ raise NotImplementedError("TODO: implement append()")
89
+
90
+ async def verify_chain(self, last_n: int = 0) -> dict:
91
+ """Verify the integrity of the hash chain."""
92
+ raise NotImplementedError("TODO: implement verify_chain()")
93
+ {%- elif plugin_type == "auth_provider" %}
94
+ async def authenticate(self, request: Any) -> dict:
95
+ """Authenticate a request; return ``{user_id, roles, metadata}``."""
96
+ raise NotImplementedError("TODO: implement authenticate()")
97
+
98
+ async def authorize(self, user: dict, action: str, resource: str = "") -> bool:
99
+ """Check whether *user* may perform *action*."""
100
+ raise NotImplementedError("TODO: implement authorize()")
101
+ {%- elif plugin_type == "pii_engine" %}
102
+ async def detect(
103
+ self,
104
+ text: str,
105
+ categories: list[str] | None = None,
106
+ ) -> list[dict]:
107
+ """Detect PII entities in *text*."""
108
+ raise NotImplementedError("TODO: implement detect()")
109
+
110
+ async def redact(self, text: str, matches: list[dict]) -> str:
111
+ """Replace detected PII spans with placeholders."""
112
+ raise NotImplementedError("TODO: implement redact()")
113
+ {%- elif plugin_type == "alert_channel" %}
114
+ async def send_alert(self, alert: dict) -> bool:
115
+ """Send a governance alert through this channel."""
116
+ raise NotImplementedError("TODO: implement send_alert()")
117
+ {%- endif %}
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "{{ plugin_name }}"
7
+ version = "0.1.0"
8
+ description = "Admina {{ plugin_type.replace('_', ' ') }} plugin: {{ plugin_name }}"
9
+ requires-python = ">=3.11"
10
+ dependencies = [
11
+ # Floor pinned to the admina-framework version that generated this
12
+ # scaffold: the plugin contract (class-attr name, async ABC methods)
13
+ # requires at least that runtime.
14
+ "admina-framework>={{ admina_version }}",
15
+ ]
16
+
17
+ [project.entry-points."admina.plugins"]
18
+ {{ plugin_name }} = "{{ plugin_name.replace('-', '_') }}"
19
+
20
+ [project.optional-dependencies]
21
+ dev = ["pytest>=8.0"]
@@ -0,0 +1,27 @@
1
+ """Tests for {{ plugin_name }} plugin."""
2
+ from __future__ import annotations
3
+
4
+ from {{ plugin_name.replace('-', '_') }} import {{ class_name }}
5
+
6
+
7
+ class Test{{ class_name }}:
8
+ """Basic tests for {{ class_name }}."""
9
+
10
+ def test_instantiation(self) -> None:
11
+ plugin = {{ class_name }}()
12
+ assert plugin is not None
13
+
14
+ def test_name(self) -> None:
15
+ plugin = {{ class_name }}()
16
+ assert plugin.name == "{{ plugin_name }}"
17
+ {%- if name_property == "supported_languages" %}
18
+
19
+ def test_supported_languages(self) -> None:
20
+ plugin = {{ class_name }}()
21
+ assert "en" in plugin.supported_languages
22
+ {%- elif name_property != "name" %}
23
+
24
+ def test_{{ name_property }}(self) -> None:
25
+ plugin = {{ class_name }}()
26
+ assert plugin.{{ name_property }} == "{{ plugin_name }}"
27
+ {%- endif %}
@@ -138,6 +138,8 @@ class FirewallConfig:
138
138
 
139
139
  enabled: bool = True
140
140
  heuristic_threshold: float = 0.7
141
+ custom_patterns: list[dict] = field(default_factory=list)
142
+ disabled_categories: list[str] = field(default_factory=list)
141
143
 
142
144
 
143
145
  @dataclass
@@ -210,7 +212,7 @@ class AdminaConfig:
210
212
  otherwise falls back to ``.env`` variables.
211
213
  """
212
214
 
213
- version: str = "2.0"
215
+ schema_version: int = 1
214
216
  data_sovereignty: DataSovereigntyConfig = field(default_factory=DataSovereigntyConfig)
215
217
  ai_infra: AIInfraConfig = field(default_factory=AIInfraConfig)
216
218
  agent_security: AgentSecurityConfig = field(default_factory=AgentSecurityConfig)
@@ -221,6 +223,7 @@ class AdminaConfig:
221
223
  pii_engine: str = "spacy-regex"
222
224
  alert_channels: list[AlertChannelConfig] = field(default_factory=list)
223
225
  plugins: list[str] = field(default_factory=list)
226
+ plugin_config: dict[str, Any] = field(default_factory=dict)
224
227
 
225
228
  # Storage — populated from .env fallback when YAML absent
226
229
  redis_url: str = "redis://localhost:6379/0"
@@ -240,6 +243,15 @@ class AdminaConfig:
240
243
  # ── YAML parsing helpers ─────────────────────────────────────
241
244
 
242
245
 
246
+ def _parse_schema_version(data: dict) -> int:
247
+ """Read ``schema_version`` (int); tolerate the legacy ``version`` key."""
248
+ raw = data.get("schema_version", data.get("version", 1))
249
+ try:
250
+ return int(raw)
251
+ except (TypeError, ValueError):
252
+ return 1
253
+
254
+
243
255
  def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
244
256
  """Build an :class:`AdminaConfig` from a parsed YAML dict."""
245
257
  domains = data.get("domains", {})
@@ -318,6 +330,8 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
318
330
  firewall=FirewallConfig(
319
331
  enabled=fw_raw.get("enabled", True),
320
332
  heuristic_threshold=fw_raw.get("heuristic_threshold", 0.7),
333
+ custom_patterns=list(fw_raw.get("custom_patterns") or []),
334
+ disabled_categories=list(fw_raw.get("disabled_categories") or []),
321
335
  ),
322
336
  loop_breaker=LoopBreakerConfig(
323
337
  enabled=lb_raw.get("enabled", True),
@@ -359,7 +373,7 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
359
373
  ]
360
374
 
361
375
  return AdminaConfig(
362
- version=data.get("version", "2.0"),
376
+ schema_version=_parse_schema_version(data),
363
377
  data_sovereignty=ds,
364
378
  ai_infra=ai,
365
379
  agent_security=agent_sec,
@@ -370,6 +384,7 @@ def _build_from_yaml(data: dict[str, Any]) -> AdminaConfig:
370
384
  pii_engine=data.get("pii_engine", "spacy-regex"),
371
385
  alert_channels=alerts,
372
386
  plugins=data.get("plugins", []),
387
+ plugin_config=data.get("plugin_config", {}),
373
388
  )
374
389
 
375
390
 
@@ -32,12 +32,52 @@ import logging
32
32
  import re
33
33
  from dataclasses import dataclass, field
34
34
  from enum import Enum
35
+ from html.parser import HTMLParser
35
36
  from pathlib import Path
36
37
  from typing import Any, Protocol, runtime_checkable
37
38
 
38
39
  logger = logging.getLogger("admina.ai_infra.rag")
39
40
 
40
41
 
42
+ class _HTMLTextExtractor(HTMLParser):
43
+ """Extract visible text from HTML, dropping ``<script>``/``<style>`` bodies.
44
+
45
+ Uses the stdlib HTML parser instead of regex tag-stripping: a real parser
46
+ handles malformed/nested tags a regex cannot (e.g. ``<scr<script>ipt>``),
47
+ so script/style content can't leak into the extracted text. This is a
48
+ best-effort ingestion extractor for RAG, not a security sanitizer — the
49
+ output is embedded as LLM context, never rendered as HTML.
50
+ """
51
+
52
+ # Substring match (not equality): a malformed payload like
53
+ # "<scr<script>ipt>" is tokenised by HTMLParser as a single start tag
54
+ # named "scr<script", so an exact "== 'script'" check would miss it.
55
+ _SKIP_TAGS = ("script", "style")
56
+
57
+ def __init__(self) -> None:
58
+ super().__init__(convert_charrefs=True)
59
+ self._parts: list[str] = []
60
+ self._skip_depth = 0
61
+
62
+ def _is_skip(self, tag: str) -> bool:
63
+ return any(s in tag for s in self._SKIP_TAGS)
64
+
65
+ def handle_starttag(self, tag: str, attrs: object) -> None:
66
+ if self._is_skip(tag):
67
+ self._skip_depth += 1
68
+
69
+ def handle_endtag(self, tag: str) -> None:
70
+ if self._is_skip(tag) and self._skip_depth > 0:
71
+ self._skip_depth -= 1
72
+
73
+ def handle_data(self, data: str) -> None:
74
+ if self._skip_depth == 0:
75
+ self._parts.append(data)
76
+
77
+ def get_text(self) -> str:
78
+ return " ".join(self._parts)
79
+
80
+
41
81
  # ── Document types ───────────────────────────────────────────
42
82
 
43
83
 
@@ -131,12 +171,15 @@ def parse_plain_text(content: str) -> str:
131
171
 
132
172
 
133
173
  def parse_html(content: str) -> str:
134
- """Strip HTML tags and return plain text."""
135
- text = re.sub(r"<script[^>]*>.*?</script>", "", content, flags=re.DOTALL)
136
- text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL)
137
- text = re.sub(r"<[^>]+>", " ", text)
138
- text = re.sub(r"\s+", " ", text).strip()
139
- return text
174
+ """Extract plain text from HTML, dropping script/style content.
175
+
176
+ Uses the stdlib HTML parser (see :class:`_HTMLTextExtractor`) rather than
177
+ regex tag-stripping, which a malformed-tag payload can bypass.
178
+ """
179
+ extractor = _HTMLTextExtractor()
180
+ extractor.feed(content)
181
+ text = extractor.get_text()
182
+ return re.sub(r"\s+", " ", text).strip()
140
183
 
141
184
 
142
185
  def parse_csv(content: str) -> str:
@@ -19,9 +19,32 @@ Automated risk classification, gap analysis, and compliance reporting.
19
19
 
20
20
  import logging
21
21
  from datetime import UTC, datetime
22
+ from typing import Any
22
23
 
23
24
  logger = logging.getLogger("admina.eu_ai_act")
24
25
 
26
+
27
+ def _coerce_checks(value: Any, n: int) -> list[bool]:
28
+ """Normalise a declared-compliance value to exactly *n* booleans.
29
+
30
+ bool → that value for the first check, the rest unmet (a single claim
31
+ does not satisfy all N checks); list → padded with False / truncated to
32
+ n; None → all unmet. Prevents a bool/short input from being zip-truncated
33
+ into a falsely high score.
34
+ """
35
+ if value is None:
36
+ return [False] * n
37
+ if isinstance(value, bool):
38
+ declared = [value]
39
+ elif isinstance(value, list):
40
+ declared = [bool(v) for v in value]
41
+ else:
42
+ declared = [False]
43
+ declared = declared[:n]
44
+ declared += [False] * (n - len(declared))
45
+ return declared
46
+
47
+
25
48
  # ── EU AI Act application timeline (Art. 113, Regulation 2024/1689) ─────────
26
49
  # Reflects the "AI Act Omnibus" agreement reached by Council and Parliament
27
50
  # on 7 May 2026 (Omnibus VII), which postponed several high-risk deadlines
@@ -288,8 +311,9 @@ class EUAIActCompliance:
288
311
  passed_checks = 0
289
312
 
290
313
  for req_key, req_info in HIGH_RISK_REQUIREMENTS.items():
291
- checks = current_compliance.get(req_key, [False] * len(req_info["checks"]))
292
- for i, (check_desc, is_met) in enumerate(zip(req_info["checks"], checks)):
314
+ n = len(req_info["checks"])
315
+ declared = _coerce_checks(current_compliance.get(req_key), n)
316
+ for i, (check_desc, is_met) in enumerate(zip(req_info["checks"], declared)):
293
317
  total_checks += 1
294
318
  if is_met:
295
319
  passed_checks += 1