admina-framework 0.9.1__tar.gz → 0.9.3__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 (114) hide show
  1. {admina_framework-0.9.1/admina_framework.egg-info → admina_framework-0.9.3}/PKG-INFO +27 -26
  2. {admina_framework-0.9.1 → admina_framework-0.9.3}/README.md +24 -23
  3. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/__init__.py +1 -1
  4. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/main.py +132 -17
  5. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/dashboard/static/index.html +37 -30
  6. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/data_sovereignty/pii.py +23 -6
  7. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/registry.py +14 -0
  8. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/api/dashboard.py +7 -5
  9. {admina_framework-0.9.1 → admina_framework-0.9.3/admina_framework.egg-info}/PKG-INFO +27 -26
  10. {admina_framework-0.9.1 → admina_framework-0.9.3}/pyproject.toml +4 -3
  11. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_domains.py +17 -0
  12. {admina_framework-0.9.1 → admina_framework-0.9.3}/LICENSE +0 -0
  13. {admina_framework-0.9.1 → admina_framework-0.9.3}/NOTICE +0 -0
  14. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/__init__.py +0 -0
  15. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/commands/__init__.py +0 -0
  16. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/admina.yaml.j2 +0 -0
  17. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/docker-compose.yml.j2 +0 -0
  18. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/env.j2 +0 -0
  19. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/main.py.j2 +0 -0
  20. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/plugin.py.j2 +0 -0
  21. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/plugin_pyproject.toml.j2 +0 -0
  22. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/plugin_readme.md.j2 +0 -0
  23. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/cli/templates/plugin_test.py.j2 +0 -0
  24. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/core/__init__.py +0 -0
  25. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/core/config.py +0 -0
  26. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/core/event_bus.py +0 -0
  27. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/core/secrets.py +0 -0
  28. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/core/types.py +0 -0
  29. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/dashboard/__init__.py +0 -0
  30. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/dashboard/static/heimdall.png +0 -0
  31. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
  32. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/__init__.py +0 -0
  33. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/agent_security/__init__.py +0 -0
  34. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/agent_security/firewall.py +0 -0
  35. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/agent_security/loop_breaker.py +0 -0
  36. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/ai_infra/__init__.py +0 -0
  37. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/ai_infra/llm_engine.py +0 -0
  38. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/ai_infra/rag.py +0 -0
  39. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/ai_infra/webui.py +0 -0
  40. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/__init__.py +0 -0
  41. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/cross_regulation.py +0 -0
  42. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/eu_ai_act.py +0 -0
  43. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/forensic.py +0 -0
  44. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/gdpr.py +0 -0
  45. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/nis2.py +0 -0
  46. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/oisg.py +0 -0
  47. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/compliance/otel.py +0 -0
  48. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/data_sovereignty/__init__.py +0 -0
  49. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/data_sovereignty/classification.py +0 -0
  50. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/domains/data_sovereignty/residency.py +0 -0
  51. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/__init__.py +0 -0
  52. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/_engines.py +0 -0
  53. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/cheshirecat/__init__.py +0 -0
  54. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
  55. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/crewai/__init__.py +0 -0
  56. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/crewai/callbacks.py +0 -0
  57. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/langchain/__init__.py +0 -0
  58. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/langchain/callbacks.py +0 -0
  59. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/n8n/__init__.py +0 -0
  60. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/integrations/openclaw/__init__.py +0 -0
  61. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/__init__.py +0 -0
  62. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/base.py +0 -0
  63. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/__init__.py +0 -0
  64. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/adapters/__init__.py +0 -0
  65. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/adapters/ollama.py +0 -0
  66. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/adapters/openai.py +0 -0
  67. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/alerts/__init__.py +0 -0
  68. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/alerts/log.py +0 -0
  69. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/alerts/webhook.py +0 -0
  70. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/auth/__init__.py +0 -0
  71. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/auth/apikey.py +0 -0
  72. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/compliance/__init__.py +0 -0
  73. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/compliance/eu_ai_act.py +0 -0
  74. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/connectors/__init__.py +0 -0
  75. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/connectors/chromadb.py +0 -0
  76. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/connectors/filesystem.py +0 -0
  77. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/forensic/__init__.py +0 -0
  78. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/forensic/filesystem.py +0 -0
  79. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/forensic/minio.py +0 -0
  80. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/guards/__init__.py +0 -0
  81. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
  82. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/pii/__init__.py +0 -0
  83. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/pii/spacy_regex.py +0 -0
  84. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/transports/__init__.py +0 -0
  85. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/transports/http_rest.py +0 -0
  86. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/plugins/builtin/transports/mcp.py +0 -0
  87. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/__init__.py +0 -0
  88. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/api/__init__.py +0 -0
  89. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/api/integration.py +0 -0
  90. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/config.py +0 -0
  91. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/engine_bridge.py +0 -0
  92. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/governance.py +0 -0
  93. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/main.py +0 -0
  94. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/multi_upstream.py +0 -0
  95. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/proxy/state.py +0 -0
  96. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/py.typed +0 -0
  97. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/sdk/__init__.py +0 -0
  98. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/sdk/_compat.py +0 -0
  99. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/sdk/compliance_kit.py +0 -0
  100. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/sdk/governed_agent.py +0 -0
  101. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/sdk/governed_data.py +0 -0
  102. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina/sdk/governed_model.py +0 -0
  103. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina_framework.egg-info/SOURCES.txt +0 -0
  104. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina_framework.egg-info/dependency_links.txt +0 -0
  105. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina_framework.egg-info/entry_points.txt +0 -0
  106. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina_framework.egg-info/requires.txt +2 -2
  107. {admina_framework-0.9.1 → admina_framework-0.9.3}/admina_framework.egg-info/top_level.txt +0 -0
  108. {admina_framework-0.9.1 → admina_framework-0.9.3}/setup.cfg +0 -0
  109. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_benchmark_14us.py +0 -0
  110. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_core_config.py +0 -0
  111. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_governance_pipeline.py +0 -0
  112. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_mcp_transport.py +0 -0
  113. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_proxy_security.py +0 -0
  114. {admina_framework-0.9.1 → admina_framework-0.9.3}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: admina-framework
3
- Version: 0.9.1
3
+ Version: 0.9.3
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>
@@ -43,10 +43,10 @@ Requires-Dist: boto3<2,>=1.34; extra == "proxy"
43
43
  Requires-Dist: minio<8,>=7.2; extra == "proxy"
44
44
  Requires-Dist: clickhouse-connect<1,>=0.7; extra == "proxy"
45
45
  Requires-Dist: typer<1,>=0.9; extra == "proxy"
46
+ Requires-Dist: numpy<2,>=1.24; extra == "proxy"
47
+ Requires-Dist: scikit-learn<2,>=1.3; extra == "proxy"
46
48
  Provides-Extra: nlp
47
49
  Requires-Dist: spacy<4,>=3.8; extra == "nlp"
48
- Requires-Dist: scikit-learn<2,>=1.3; extra == "nlp"
49
- Requires-Dist: numpy<2,>=1.24; extra == "nlp"
50
50
  Provides-Extra: telemetry
51
51
  Requires-Dist: opentelemetry-api<2,>=1.20; extra == "telemetry"
52
52
  Requires-Dist: opentelemetry-sdk<2,>=1.20; extra == "telemetry"
@@ -120,20 +120,22 @@ report = kit.gap_analysis(risk_category="high", current_compliance={...})
120
120
  ### Install from PyPI
121
121
 
122
122
  ```bash
123
- # SDK only (lightweight, pure Python)
124
- pip install admina-framework
125
-
126
- # Proxy + infrastructure deps
123
+ # Recommended for new users: SDK + proxy + dashboard.
124
+ # Lets you run `admina dev` and see the dashboard out of the box.
127
125
  pip install "admina-framework[proxy]"
128
126
 
129
- # Everything (proxy + NLP + telemetry)
127
+ # Everything (proxy + NLP + telemetry). Use this if you also want
128
+ # spaCy-based NER for PII detection or OpenTelemetry export.
130
129
  pip install "admina-framework[full]"
130
+ python -m spacy download en_core_web_sm # for [full] only
131
131
 
132
- # After [nlp] / [full] install: download the spaCy NER model
133
- python -m spacy download en_core_web_sm
134
-
135
- # Optional: Rust-accelerated engine (auto-detected at runtime)
132
+ # Optional: Rust-accelerated engine (auto-detected at runtime).
136
133
  pip install admina-core
134
+
135
+ # Advanced: SDK only (no proxy, no dashboard, no `admina dev`).
136
+ # Use this when embedding the SDK into another service and you don't
137
+ # need the local dev server.
138
+ pip install admina-framework
137
139
  ```
138
140
 
139
141
  > The PyPI distribution name is `admina-framework`; the Python import
@@ -151,28 +153,27 @@ pip install admina-core
151
153
  git clone https://github.com/admina-org/admina.git
152
154
  cd admina
153
155
 
154
- # Option 1: SDK only (lightweight)
155
- pip install -e .
156
- python -c "from admina import GovernedModel; print('SDK ready')"
157
-
158
- # Note: To use the OllamaAdapter, install Ollama (https://ollama.ai)
159
- # and pull a model first: ollama pull llama3.1:8b
160
-
161
- # Option 2: Proxy + infra deps
156
+ # Recommended: proxy + dashboard + infra deps (enables `admina dev`)
162
157
  pip install -e ".[proxy]"
163
158
 
164
- # Option 3: Everything (proxy + NLP + telemetry)
159
+ # Everything (proxy + NLP + telemetry)
165
160
  pip install -e ".[full]"
166
161
 
167
- # Option 4: Full stack (proxy + dashboard + infra via Docker)
162
+ # CLI workflow
163
+ admina init my-project # Scaffold a governed AI project
164
+ cd my-project # admina dev runs from the project directory
165
+ admina dev # Start the local proxy + dashboard
166
+
167
+ # Full stack via Docker (no [proxy] extra required)
168
168
  ./scripts/bootstrap-secrets.sh # Auto-generate .env with random credentials
169
169
  docker compose up --build # Credentials printed at bootstrap
170
170
 
171
- # Option 5: CLI
171
+ # Note: To use the OllamaAdapter, install Ollama (https://ollama.ai)
172
+ # and pull a model first: ollama pull llama3.1:8b
173
+
174
+ # Advanced: SDK only (no proxy, no dashboard)
172
175
  pip install -e .
173
- admina init my-project # Scaffold a governed AI project
174
- cd my-project # admina dev runs from the project directory
175
- admina dev # Start local dev stack
176
+ python -c "from admina import GovernedModel; print('SDK ready')"
176
177
  ```
177
178
 
178
179
  Dashboard: [http://localhost:3000](http://localhost:3000) | API docs: [http://localhost:8080/docs](http://localhost:8080/docs)
@@ -62,20 +62,22 @@ report = kit.gap_analysis(risk_category="high", current_compliance={...})
62
62
  ### Install from PyPI
63
63
 
64
64
  ```bash
65
- # SDK only (lightweight, pure Python)
66
- pip install admina-framework
67
-
68
- # Proxy + infrastructure deps
65
+ # Recommended for new users: SDK + proxy + dashboard.
66
+ # Lets you run `admina dev` and see the dashboard out of the box.
69
67
  pip install "admina-framework[proxy]"
70
68
 
71
- # Everything (proxy + NLP + telemetry)
69
+ # Everything (proxy + NLP + telemetry). Use this if you also want
70
+ # spaCy-based NER for PII detection or OpenTelemetry export.
72
71
  pip install "admina-framework[full]"
72
+ python -m spacy download en_core_web_sm # for [full] only
73
73
 
74
- # After [nlp] / [full] install: download the spaCy NER model
75
- python -m spacy download en_core_web_sm
76
-
77
- # Optional: Rust-accelerated engine (auto-detected at runtime)
74
+ # Optional: Rust-accelerated engine (auto-detected at runtime).
78
75
  pip install admina-core
76
+
77
+ # Advanced: SDK only (no proxy, no dashboard, no `admina dev`).
78
+ # Use this when embedding the SDK into another service and you don't
79
+ # need the local dev server.
80
+ pip install admina-framework
79
81
  ```
80
82
 
81
83
  > The PyPI distribution name is `admina-framework`; the Python import
@@ -93,28 +95,27 @@ pip install admina-core
93
95
  git clone https://github.com/admina-org/admina.git
94
96
  cd admina
95
97
 
96
- # Option 1: SDK only (lightweight)
97
- pip install -e .
98
- python -c "from admina import GovernedModel; print('SDK ready')"
99
-
100
- # Note: To use the OllamaAdapter, install Ollama (https://ollama.ai)
101
- # and pull a model first: ollama pull llama3.1:8b
102
-
103
- # Option 2: Proxy + infra deps
98
+ # Recommended: proxy + dashboard + infra deps (enables `admina dev`)
104
99
  pip install -e ".[proxy]"
105
100
 
106
- # Option 3: Everything (proxy + NLP + telemetry)
101
+ # Everything (proxy + NLP + telemetry)
107
102
  pip install -e ".[full]"
108
103
 
109
- # Option 4: Full stack (proxy + dashboard + infra via Docker)
104
+ # CLI workflow
105
+ admina init my-project # Scaffold a governed AI project
106
+ cd my-project # admina dev runs from the project directory
107
+ admina dev # Start the local proxy + dashboard
108
+
109
+ # Full stack via Docker (no [proxy] extra required)
110
110
  ./scripts/bootstrap-secrets.sh # Auto-generate .env with random credentials
111
111
  docker compose up --build # Credentials printed at bootstrap
112
112
 
113
- # Option 5: CLI
113
+ # Note: To use the OllamaAdapter, install Ollama (https://ollama.ai)
114
+ # and pull a model first: ollama pull llama3.1:8b
115
+
116
+ # Advanced: SDK only (no proxy, no dashboard)
114
117
  pip install -e .
115
- admina init my-project # Scaffold a governed AI project
116
- cd my-project # admina dev runs from the project directory
117
- admina dev # Start local dev stack
118
+ python -c "from admina import GovernedModel; print('SDK ready')"
118
119
  ```
119
120
 
120
121
  Dashboard: [http://localhost:3000](http://localhost:3000) | API docs: [http://localhost:8080/docs](http://localhost:8080/docs)
@@ -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.1"
26
+ __version__ = "0.9.3"
27
27
 
28
28
  __all__ = [
29
29
  "__version__",
@@ -262,20 +262,61 @@ def init(
262
262
  else:
263
263
  click.echo(" ⚠ docker compose pull failed (you can run it later)")
264
264
 
265
- # 5. Print next steps
266
- click.echo(f"""
267
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
268
- Project ready!
265
+ # 5. Print next steps — tailored to what the user actually has installed.
266
+ click.echo(_format_next_steps(project_name))
269
267
 
270
- Next steps:
271
- cd {project_name}
272
- admina dev # local mode (no Docker): proxy + dashboard on :3000
273
- admina dev --stack # full Docker stack (proxy, redis, clickhouse, minio, grafana)
274
- python main.py # run example (works without admina dev)
275
268
 
276
- Docs: https://admina.org/docs
277
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
278
- """)
269
+ def _format_next_steps(project_name: str) -> str:
270
+ """Build the post-init "Next steps" message based on detected extras.
271
+
272
+ Honest output: only suggest commands that will work with the user's
273
+ current install. Missing prerequisites are surfaced explicitly with
274
+ the exact upgrade command.
275
+ """
276
+ proxy_ok = _proxy_extra_installed()
277
+ docker_ok = shutil.which("docker") is not None
278
+
279
+ lines: list[str] = [
280
+ "",
281
+ " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
282
+ " Project ready!",
283
+ "",
284
+ " Next steps:",
285
+ f" cd {project_name}",
286
+ " python main.py # SDK example — works with any install",
287
+ ]
288
+ if proxy_ok:
289
+ lines.append(" admina dev # local proxy + dashboard on :3000")
290
+ else:
291
+ lines.extend(
292
+ [
293
+ "",
294
+ " To run the local proxy + dashboard (admina dev), install the [proxy] extra:",
295
+ " pip install 'admina-framework[proxy]' --upgrade",
296
+ ]
297
+ )
298
+ if docker_ok:
299
+ lines.append(
300
+ " admina dev --stack # full Docker stack "
301
+ "(proxy + redis + clickhouse + minio + grafana)"
302
+ )
303
+ else:
304
+ lines.extend(
305
+ [
306
+ "",
307
+ " To run the full Docker stack (admina dev --stack), install Docker:",
308
+ " https://docs.docker.com/get-docker/",
309
+ ]
310
+ )
311
+ lines.extend(
312
+ [
313
+ "",
314
+ " Docs: https://admina.org/docs",
315
+ " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
316
+ "",
317
+ ]
318
+ )
319
+ return "\n".join(lines)
279
320
 
280
321
 
281
322
  # ── admina dev helpers ─────────────────────────────────────
@@ -482,6 +523,46 @@ def _list_local_ipv4() -> list[str]:
482
523
  return sorted(ips, key=lambda ip: (ip != "127.0.0.1", ip))
483
524
 
484
525
 
526
+ def _proxy_extra_installed() -> bool:
527
+ """True if uvicorn + fastapi (the [proxy] extra) are importable."""
528
+ try:
529
+ import fastapi # noqa: F401
530
+ import uvicorn # noqa: F401
531
+ except ImportError:
532
+ return False
533
+ return True
534
+
535
+
536
+ def _require_proxy_extra_for_local_dev() -> None:
537
+ """Exit cleanly with an actionable message when [proxy] is missing.
538
+
539
+ Local-mode `admina dev` shells out to uvicorn, which lives only in the
540
+ [proxy] extra. Without an early check, the user sees a cryptic
541
+ `No module named uvicorn` from python -m uvicorn.
542
+ """
543
+ if _proxy_extra_installed():
544
+ return
545
+ click.echo("", err=True)
546
+ click.echo(" admina dev (local mode) requires the [proxy] extra.", err=True)
547
+ click.echo(" Install one of:", err=True)
548
+ click.echo(
549
+ " pip install 'admina-framework[proxy]' --upgrade",
550
+ err=True,
551
+ )
552
+ click.echo(
553
+ " pip install 'admina-framework[full]' --upgrade # adds NLP + telemetry",
554
+ err=True,
555
+ )
556
+ click.echo("", err=True)
557
+ click.echo(
558
+ " Or run the full Docker stack (no [proxy] extra required):",
559
+ err=True,
560
+ )
561
+ click.echo(" admina dev --stack", err=True)
562
+ click.echo("", err=True)
563
+ raise SystemExit(1)
564
+
565
+
485
566
  def _run_local(
486
567
  project_dir: Path,
487
568
  vault: SecretVault,
@@ -491,6 +572,8 @@ def _run_local(
491
572
  host: str,
492
573
  ) -> None:
493
574
  """Run proxy + dashboard as a single uvicorn process (no Docker)."""
575
+ _require_proxy_extra_for_local_dev()
576
+
494
577
  # Auto-detect free port: if preferred is taken (Docker Desktop, Grafana,
495
578
  # another node dev server on :3000…), fall back to the next free port.
496
579
  try:
@@ -1032,6 +1115,8 @@ def doctor() -> None:
1032
1115
  issues.append(f"Missing: pip install {pkg_name}")
1033
1116
 
1034
1117
  # ── Optional extras ──────────────────────────────────────
1118
+ # Each group lists (import_name, pypi_name) tuples. numpy + scikit-learn
1119
+ # are part of [proxy] (LoopBreaker requires them), not [nlp].
1035
1120
  click.echo("\n Optional extras:")
1036
1121
  extras = {
1037
1122
  "proxy": [
@@ -1042,16 +1127,17 @@ def doctor() -> None:
1042
1127
  ("redis", "redis"),
1043
1128
  ("minio", "minio"),
1044
1129
  ("clickhouse_connect", "clickhouse-connect"),
1130
+ ("numpy", "numpy"),
1131
+ ("sklearn", "scikit-learn"),
1045
1132
  ],
1046
1133
  "nlp": [
1047
1134
  ("spacy", "spacy"),
1048
- ("sklearn", "scikit-learn"),
1049
- ("numpy", "numpy"),
1050
1135
  ],
1051
1136
  "telemetry": [
1052
1137
  ("opentelemetry", "opentelemetry-api"),
1053
1138
  ],
1054
1139
  }
1140
+ extras_status: dict[str, str] = {}
1055
1141
  for group, mods in extras.items():
1056
1142
  present = 0
1057
1143
  for mod_name, _ in mods:
@@ -1062,12 +1148,25 @@ def doctor() -> None:
1062
1148
  pass
1063
1149
  if present == len(mods):
1064
1150
  click.echo(f" [{group}]{' ' * (16 - len(group))} {ok_mark} ({present}/{len(mods)})")
1151
+ extras_status[group] = "ok"
1065
1152
  elif present > 0:
1066
1153
  click.echo(
1067
1154
  f" [{group}]{' ' * (16 - len(group))} {warn_mark} ({present}/{len(mods)})"
1068
1155
  )
1156
+ extras_status[group] = "partial"
1069
1157
  else:
1070
1158
  click.echo(f" [{group}]{' ' * (16 - len(group))} -- not installed")
1159
+ extras_status[group] = "missing"
1160
+
1161
+ if extras_status.get("proxy") != "ok":
1162
+ click.echo(
1163
+ f" {warn_mark} admina dev (local mode) needs the [proxy] extra — "
1164
+ "pip install 'admina-framework[proxy]' --upgrade"
1165
+ )
1166
+ issues.append(
1167
+ "admina dev (local mode) requires the [proxy] extra — "
1168
+ "run: pip install 'admina-framework[proxy]' --upgrade"
1169
+ )
1071
1170
 
1072
1171
  # ── spaCy NER model ──────────────────────────────────────
1073
1172
  click.echo("\n NLP engine:")
@@ -1078,12 +1177,28 @@ def doctor() -> None:
1078
1177
  spacy.load("en_core_web_sm")
1079
1178
  click.echo(f" en_core_web_sm {ok_mark}")
1080
1179
  except OSError:
1180
+ # PII still works in regex-only mode without the spaCy model, so
1181
+ # this is a soft warning rather than a blocking issue.
1081
1182
  click.echo(
1082
- f" en_core_web_sm {warn_mark} (run: python -m spacy download en_core_web_sm)"
1183
+ f" en_core_web_sm {warn_mark} not loadable "
1184
+ "(PII falls back to regex-only — install for NER coverage)"
1185
+ )
1186
+ # The model is not on PyPI — it ships as a direct wheel URL
1187
+ # from explosion/spacy-models on GitHub. `python -m spacy
1188
+ # download` is the canonical command but needs pip in the
1189
+ # venv; for uv venvs (which omit pip) point at the wheel URL.
1190
+ model_ver = "3.8.0" # matches spacy>=3.8,<4 pinned in [nlp]
1191
+ wheel_url = (
1192
+ "https://github.com/explosion/spacy-models/releases/download/"
1193
+ f"en_core_web_sm-{model_ver}/en_core_web_sm-{model_ver}-py3-none-any.whl"
1083
1194
  )
1084
- issues.append("spaCy model not installed PII uses regex-only mode")
1195
+ click.echo(f" Install with: {sys.executable} -m spacy download en_core_web_sm")
1196
+ click.echo(f" For uv venvs: uv pip install {wheel_url}")
1085
1197
  except ImportError:
1086
- click.echo(" spacy -- not installed (PII disabled)")
1198
+ click.echo(
1199
+ " spacy -- not installed "
1200
+ "(PII falls back to regex-only — install [nlp] extra for NER)"
1201
+ )
1087
1202
 
1088
1203
  # ── Rust engine ──────────────────────────────────────────
1089
1204
  click.echo("\n Governance engine:")
@@ -932,36 +932,43 @@ function dashboard() {
932
932
 
933
933
  // ── Fetch all data ─────────────────────────────────
934
934
  async refresh() {
935
- try {
936
- const [scoreRes, compRes, sovRes, statsRes, feedRes, infraRes, modelsRes, oisgRes] = await Promise.all([
937
- fetch('/api/dashboard/score').then(r => r.json()),
938
- fetch('/api/dashboard/compliance').then(r => r.json()),
939
- fetch('/api/dashboard/sovereignty').then(r => r.json()),
940
- fetch('/api/stats').then(r => r.json()),
941
- fetch('/api/dashboard/feed?limit=50').then(r => r.json()),
942
- fetch('/api/dashboard/infra').then(r => r.json()),
943
- fetch('/api/dashboard/models').then(r => r.json()),
944
- fetch('/api/dashboard/oisg').then(r => r.json()).catch(() => ({})),
945
- ]);
946
-
947
- this.score = scoreRes;
948
- this.complianceData = compRes;
949
- this.sovereignty = sovRes;
950
- this.stats = statsRes;
951
- this.infraData = infraRes;
952
- this.modelData = modelsRes;
953
- this.oisg = oisgRes;
954
- this.healthy = true;
955
- this.lastUpdate = new Date().toLocaleTimeString();
956
-
957
- // Seed the feed with historical events if WS hasn't provided any yet
958
- if (this.feedEvents.length === 0 && feedRes.events && feedRes.events.length > 0) {
959
- this.feedEvents = feedRes.events.slice(0, 50).map(e => this._formatFeedEvent(e));
960
- }
961
- } catch (err) {
962
- console.error('Dashboard refresh failed:', err);
963
- this.healthy = false;
964
- this.lastUpdate = 'error';
935
+ // allSettled so one failing endpoint never blanks the whole dashboard.
936
+ const okJson = async (url) => {
937
+ const r = await fetch(url);
938
+ if (!r.ok) throw new Error(`${url} -> ${r.status}`);
939
+ return r.json();
940
+ };
941
+ const results = await Promise.allSettled([
942
+ okJson('/api/dashboard/score'),
943
+ okJson('/api/dashboard/compliance'),
944
+ okJson('/api/dashboard/sovereignty'),
945
+ okJson('/api/stats'),
946
+ okJson('/api/dashboard/feed?limit=50'),
947
+ okJson('/api/dashboard/infra'),
948
+ okJson('/api/dashboard/models'),
949
+ okJson('/api/dashboard/oisg'),
950
+ ]);
951
+ const [scoreRes, compRes, sovRes, statsRes, feedRes, infraRes, modelsRes, oisgRes] =
952
+ results.map(r => r.status === 'fulfilled' ? r.value : null);
953
+
954
+ if (scoreRes) this.score = scoreRes;
955
+ if (compRes) this.complianceData = compRes;
956
+ if (sovRes) this.sovereignty = sovRes;
957
+ if (statsRes) this.stats = statsRes;
958
+ if (infraRes) this.infraData = infraRes;
959
+ if (modelsRes) this.modelData = modelsRes;
960
+ if (oisgRes) this.oisg = oisgRes;
961
+
962
+ const failed = results.filter(r => r.status === 'rejected');
963
+ if (failed.length) {
964
+ failed.forEach(r => console.warn('[Admina] endpoint failed:', r.reason));
965
+ }
966
+ this.healthy = failed.length === 0;
967
+ this.lastUpdate = new Date().toLocaleTimeString();
968
+
969
+ // Seed the feed with historical events if WS hasn't provided any yet
970
+ if (this.feedEvents.length === 0 && feedRes?.events?.length > 0) {
971
+ this.feedEvents = feedRes.events.slice(0, 50).map(e => this._formatFeedEvent(e));
965
972
  }
966
973
  },
967
974
 
@@ -21,7 +21,12 @@ import logging
21
21
  import os
22
22
  import re
23
23
 
24
- import spacy
24
+ # spaCy is part of the [nlp] extra. When absent, PIIRedactor falls back
25
+ # to regex-only mode (still covers EMAIL/PHONE/SSN/IBAN/IP/credit-card/EU IDs).
26
+ try:
27
+ import spacy as _spacy
28
+ except ImportError:
29
+ _spacy = None # type: ignore[assignment]
25
30
 
26
31
  logger = logging.getLogger("admina.pii_redactor")
27
32
 
@@ -152,12 +157,24 @@ class PIIRedactor:
152
157
  def __init__(self, config=None):
153
158
  # Resolve NLP model: config.ner_model > ADMINA_SPACY_MODEL env var > default
154
159
  model_name = getattr(config, "ner_model", None) or SPACY_MODEL
155
- try:
156
- self.nlp = spacy.load(model_name)
157
- logger.info("[OK] spaCy model loaded: %s", model_name)
158
- except OSError:
159
- logger.warning("[WARN] spaCy model '%s' not found, using regex-only mode", model_name)
160
+ if _spacy is None:
161
+ logger.info(
162
+ "spaCy not installed — PII redaction running in regex-only mode "
163
+ "(install admina-framework[nlp] for NER-based detection)"
164
+ )
160
165
  self.nlp = None
166
+ else:
167
+ try:
168
+ self.nlp = _spacy.load(model_name)
169
+ logger.info("[OK] spaCy model loaded: %s", model_name)
170
+ except OSError:
171
+ logger.warning(
172
+ "spaCy model '%s' not found — using regex-only mode "
173
+ "(run: python -m spacy download %s)",
174
+ model_name,
175
+ model_name,
176
+ )
177
+ self.nlp = None
161
178
 
162
179
  # Build active categories: start from PII_CATEGORIES defaults, then
163
180
  # disable any category not listed in config.categories (if provided).
@@ -284,6 +284,13 @@ class PluginRegistry:
284
284
  mod = importlib.util.module_from_spec(spec)
285
285
  sys.modules[mod_name] = mod
286
286
  spec.loader.exec_module(mod)
287
+ except ModuleNotFoundError as exc:
288
+ logger.warning(
289
+ "Skipping plugin %s — optional dependency %r not installed",
290
+ py_file.stem,
291
+ exc.name or "?",
292
+ )
293
+ return 0
287
294
  except (ImportError, AttributeError, RuntimeError):
288
295
  logger.warning("Failed to import plugin file %s", py_file, exc_info=True)
289
296
  return 0
@@ -294,6 +301,13 @@ class PluginRegistry:
294
301
  """Import a module by dotted path and register all plugin classes."""
295
302
  try:
296
303
  mod = importlib.import_module(mod_path)
304
+ except ModuleNotFoundError as exc:
305
+ logger.warning(
306
+ "Skipping plugin module %r — optional dependency %r not installed",
307
+ mod_path,
308
+ exc.name or "?",
309
+ )
310
+ return 0
297
311
  except ImportError:
298
312
  logger.warning("Failed to import plugin module %r", mod_path, exc_info=True)
299
313
  return 0
@@ -450,9 +450,13 @@ def create_dashboard_endpoints(
450
450
 
451
451
  # Upstream MCP
452
452
  http = get_http_client() if get_http_client else None
453
- if http is not None:
454
- upstream = get_settings().UPSTREAM_MCP_URL
453
+ upstream = get_settings().UPSTREAM_MCP_URL if get_settings else ""
454
+ if http is None or not upstream.startswith(("http://", "https://")):
455
+ services["upstream_mcp"] = {"status": "not_configured"}
456
+ else:
455
457
  try:
458
+ import httpx
459
+
456
460
  t0 = time.perf_counter()
457
461
  resp = await http.get(f"{upstream}/health", timeout=3.0)
458
462
  latency = round((time.perf_counter() - t0) * 1000, 2)
@@ -461,13 +465,11 @@ def create_dashboard_endpoints(
461
465
  "latency_ms": latency,
462
466
  "url": upstream,
463
467
  }
464
- except (OSError, RuntimeError):
468
+ except (OSError, RuntimeError, httpx.HTTPError):
465
469
  services["upstream_mcp"] = {
466
470
  "status": "unreachable",
467
471
  "url": upstream,
468
472
  }
469
- else:
470
- services["upstream_mcp"] = {"status": "not_configured"}
471
473
 
472
474
  healthy_count = sum(1 for s in services.values() if s.get("status") == "healthy")
473
475
  return {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: admina-framework
3
- Version: 0.9.1
3
+ Version: 0.9.3
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>
@@ -43,10 +43,10 @@ Requires-Dist: boto3<2,>=1.34; extra == "proxy"
43
43
  Requires-Dist: minio<8,>=7.2; extra == "proxy"
44
44
  Requires-Dist: clickhouse-connect<1,>=0.7; extra == "proxy"
45
45
  Requires-Dist: typer<1,>=0.9; extra == "proxy"
46
+ Requires-Dist: numpy<2,>=1.24; extra == "proxy"
47
+ Requires-Dist: scikit-learn<2,>=1.3; extra == "proxy"
46
48
  Provides-Extra: nlp
47
49
  Requires-Dist: spacy<4,>=3.8; extra == "nlp"
48
- Requires-Dist: scikit-learn<2,>=1.3; extra == "nlp"
49
- Requires-Dist: numpy<2,>=1.24; extra == "nlp"
50
50
  Provides-Extra: telemetry
51
51
  Requires-Dist: opentelemetry-api<2,>=1.20; extra == "telemetry"
52
52
  Requires-Dist: opentelemetry-sdk<2,>=1.20; extra == "telemetry"
@@ -120,20 +120,22 @@ report = kit.gap_analysis(risk_category="high", current_compliance={...})
120
120
  ### Install from PyPI
121
121
 
122
122
  ```bash
123
- # SDK only (lightweight, pure Python)
124
- pip install admina-framework
125
-
126
- # Proxy + infrastructure deps
123
+ # Recommended for new users: SDK + proxy + dashboard.
124
+ # Lets you run `admina dev` and see the dashboard out of the box.
127
125
  pip install "admina-framework[proxy]"
128
126
 
129
- # Everything (proxy + NLP + telemetry)
127
+ # Everything (proxy + NLP + telemetry). Use this if you also want
128
+ # spaCy-based NER for PII detection or OpenTelemetry export.
130
129
  pip install "admina-framework[full]"
130
+ python -m spacy download en_core_web_sm # for [full] only
131
131
 
132
- # After [nlp] / [full] install: download the spaCy NER model
133
- python -m spacy download en_core_web_sm
134
-
135
- # Optional: Rust-accelerated engine (auto-detected at runtime)
132
+ # Optional: Rust-accelerated engine (auto-detected at runtime).
136
133
  pip install admina-core
134
+
135
+ # Advanced: SDK only (no proxy, no dashboard, no `admina dev`).
136
+ # Use this when embedding the SDK into another service and you don't
137
+ # need the local dev server.
138
+ pip install admina-framework
137
139
  ```
138
140
 
139
141
  > The PyPI distribution name is `admina-framework`; the Python import
@@ -151,28 +153,27 @@ pip install admina-core
151
153
  git clone https://github.com/admina-org/admina.git
152
154
  cd admina
153
155
 
154
- # Option 1: SDK only (lightweight)
155
- pip install -e .
156
- python -c "from admina import GovernedModel; print('SDK ready')"
157
-
158
- # Note: To use the OllamaAdapter, install Ollama (https://ollama.ai)
159
- # and pull a model first: ollama pull llama3.1:8b
160
-
161
- # Option 2: Proxy + infra deps
156
+ # Recommended: proxy + dashboard + infra deps (enables `admina dev`)
162
157
  pip install -e ".[proxy]"
163
158
 
164
- # Option 3: Everything (proxy + NLP + telemetry)
159
+ # Everything (proxy + NLP + telemetry)
165
160
  pip install -e ".[full]"
166
161
 
167
- # Option 4: Full stack (proxy + dashboard + infra via Docker)
162
+ # CLI workflow
163
+ admina init my-project # Scaffold a governed AI project
164
+ cd my-project # admina dev runs from the project directory
165
+ admina dev # Start the local proxy + dashboard
166
+
167
+ # Full stack via Docker (no [proxy] extra required)
168
168
  ./scripts/bootstrap-secrets.sh # Auto-generate .env with random credentials
169
169
  docker compose up --build # Credentials printed at bootstrap
170
170
 
171
- # Option 5: CLI
171
+ # Note: To use the OllamaAdapter, install Ollama (https://ollama.ai)
172
+ # and pull a model first: ollama pull llama3.1:8b
173
+
174
+ # Advanced: SDK only (no proxy, no dashboard)
172
175
  pip install -e .
173
- admina init my-project # Scaffold a governed AI project
174
- cd my-project # admina dev runs from the project directory
175
- admina dev # Start local dev stack
176
+ python -c "from admina import GovernedModel; print('SDK ready')"
176
177
  ```
177
178
 
178
179
  Dashboard: [http://localhost:3000](http://localhost:3000) | API docs: [http://localhost:8080/docs](http://localhost:8080/docs)
@@ -19,7 +19,7 @@ build-backend = "setuptools.build_meta"
19
19
 
20
20
  [project]
21
21
  name = "admina-framework"
22
- version = "0.9.1"
22
+ version = "0.9.3"
23
23
  description = "Admina — governed AI development framework"
24
24
  readme = "README.md"
25
25
  requires-python = ">=3.11"
@@ -80,6 +80,9 @@ proxy = [
80
80
  "minio>=7.2,<8",
81
81
  "clickhouse-connect>=0.7,<1",
82
82
  "typer>=0.9,<1",
83
+ # Required by the LoopBreaker (core proxy guardrail — not an NLP add-on).
84
+ "numpy>=1.24,<2",
85
+ "scikit-learn>=1.3,<2",
83
86
  ]
84
87
  nlp = [
85
88
  # spacy 3.7.x ships an old blis (0.7.11) that fails to build on Python 3.13
@@ -92,8 +95,6 @@ nlp = [
92
95
  # `admina-framework[nlp]`, run:
93
96
  # python -m spacy download en_core_web_sm
94
97
  # The PIIRedactor logs a clear error if the model is missing.
95
- "scikit-learn>=1.3,<2",
96
- "numpy>=1.24,<2",
97
98
  ]
98
99
  telemetry = [
99
100
  "opentelemetry-api>=1.20,<2",
@@ -295,6 +295,23 @@ class TestEngineBridge:
295
295
  assert hasattr(pii, "redact")
296
296
  assert hasattr(pii, "get_stats")
297
297
 
298
+ def test_pii_factory_without_spacy_installed(self, monkeypatch):
299
+ """Regression: admina dev must boot even when spaCy is not installed.
300
+
301
+ Simulates a user who ran `pip install admina-framework[proxy]` without
302
+ the [nlp] extra. The PII bridge must fall back to regex-only mode
303
+ instead of crashing the proxy lifespan with ModuleNotFoundError.
304
+ """
305
+ from admina.domains.data_sovereignty import pii as pii_mod
306
+
307
+ monkeypatch.setattr(pii_mod, "_spacy", None)
308
+
309
+ redactor = pii_mod.PIIRedactor()
310
+ assert redactor.nlp is None
311
+ r = redactor.redact("Contact john@example.com")
312
+ assert "john@example.com" not in r["redacted_text"]
313
+ assert r["count"] >= 1
314
+
298
315
  def test_loop_breaker_factory(self):
299
316
  from admina.proxy.engine_bridge import get_loop_breaker
300
317
 
@@ -8,8 +8,6 @@ admina-framework[nlp,proxy,telemetry]
8
8
 
9
9
  [nlp]
10
10
  spacy<4,>=3.8
11
- scikit-learn<2,>=1.3
12
- numpy<2,>=1.24
13
11
 
14
12
  [proxy]
15
13
  fastapi<1,>=0.104
@@ -23,6 +21,8 @@ boto3<2,>=1.34
23
21
  minio<8,>=7.2
24
22
  clickhouse-connect<1,>=0.7
25
23
  typer<1,>=0.9
24
+ numpy<2,>=1.24
25
+ scikit-learn<2,>=1.3
26
26
 
27
27
  [telemetry]
28
28
  opentelemetry-api<2,>=1.20