admina-framework 0.9.0__tar.gz → 0.9.2__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.0/admina_framework.egg-info → admina_framework-0.9.2}/PKG-INFO +3 -3
  2. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/__init__.py +1 -1
  3. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/dashboard/static/index.html +37 -30
  4. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/data_sovereignty/pii.py +23 -6
  5. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/registry.py +14 -0
  6. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/api/dashboard.py +7 -5
  7. {admina_framework-0.9.0 → admina_framework-0.9.2/admina_framework.egg-info}/PKG-INFO +3 -3
  8. {admina_framework-0.9.0 → admina_framework-0.9.2}/pyproject.toml +4 -3
  9. {admina_framework-0.9.0 → admina_framework-0.9.2}/tests/test_domains.py +17 -0
  10. {admina_framework-0.9.0 → admina_framework-0.9.2}/LICENSE +0 -0
  11. {admina_framework-0.9.0 → admina_framework-0.9.2}/NOTICE +0 -0
  12. {admina_framework-0.9.0 → admina_framework-0.9.2}/README.md +0 -0
  13. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/__init__.py +0 -0
  14. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/commands/__init__.py +0 -0
  15. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/main.py +0 -0
  16. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/admina.yaml.j2 +0 -0
  17. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/docker-compose.yml.j2 +0 -0
  18. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/env.j2 +0 -0
  19. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/main.py.j2 +0 -0
  20. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/plugin.py.j2 +0 -0
  21. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/plugin_pyproject.toml.j2 +0 -0
  22. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/plugin_readme.md.j2 +0 -0
  23. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/cli/templates/plugin_test.py.j2 +0 -0
  24. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/core/__init__.py +0 -0
  25. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/core/config.py +0 -0
  26. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/core/event_bus.py +0 -0
  27. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/core/secrets.py +0 -0
  28. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/core/types.py +0 -0
  29. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/dashboard/__init__.py +0 -0
  30. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/dashboard/static/heimdall.png +0 -0
  31. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/dashboard/static/vendor/alpinejs.min.js +0 -0
  32. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/__init__.py +0 -0
  33. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/agent_security/__init__.py +0 -0
  34. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/agent_security/firewall.py +0 -0
  35. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/agent_security/loop_breaker.py +0 -0
  36. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/ai_infra/__init__.py +0 -0
  37. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/ai_infra/llm_engine.py +0 -0
  38. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/ai_infra/rag.py +0 -0
  39. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/ai_infra/webui.py +0 -0
  40. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/__init__.py +0 -0
  41. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/cross_regulation.py +0 -0
  42. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/eu_ai_act.py +0 -0
  43. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/forensic.py +0 -0
  44. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/gdpr.py +0 -0
  45. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/nis2.py +0 -0
  46. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/oisg.py +0 -0
  47. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/compliance/otel.py +0 -0
  48. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/data_sovereignty/__init__.py +0 -0
  49. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/data_sovereignty/classification.py +0 -0
  50. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/domains/data_sovereignty/residency.py +0 -0
  51. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/__init__.py +0 -0
  52. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/_engines.py +0 -0
  53. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/cheshirecat/__init__.py +0 -0
  54. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/cheshirecat/admina-plugin/admina_governance.py +0 -0
  55. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/crewai/__init__.py +0 -0
  56. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/crewai/callbacks.py +0 -0
  57. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/langchain/__init__.py +0 -0
  58. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/langchain/callbacks.py +0 -0
  59. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/n8n/__init__.py +0 -0
  60. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/integrations/openclaw/__init__.py +0 -0
  61. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/__init__.py +0 -0
  62. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/base.py +0 -0
  63. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/__init__.py +0 -0
  64. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/adapters/__init__.py +0 -0
  65. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/adapters/ollama.py +0 -0
  66. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/adapters/openai.py +0 -0
  67. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/alerts/__init__.py +0 -0
  68. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/alerts/log.py +0 -0
  69. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/alerts/webhook.py +0 -0
  70. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/auth/__init__.py +0 -0
  71. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/auth/apikey.py +0 -0
  72. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/compliance/__init__.py +0 -0
  73. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/compliance/eu_ai_act.py +0 -0
  74. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/connectors/__init__.py +0 -0
  75. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/connectors/chromadb.py +0 -0
  76. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/connectors/filesystem.py +0 -0
  77. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/forensic/__init__.py +0 -0
  78. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/forensic/filesystem.py +0 -0
  79. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/forensic/minio.py +0 -0
  80. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/guards/__init__.py +0 -0
  81. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/guards/guardrailsai_guard.py +0 -0
  82. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/pii/__init__.py +0 -0
  83. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/pii/spacy_regex.py +0 -0
  84. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/transports/__init__.py +0 -0
  85. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/transports/http_rest.py +0 -0
  86. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/plugins/builtin/transports/mcp.py +0 -0
  87. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/__init__.py +0 -0
  88. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/api/__init__.py +0 -0
  89. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/api/integration.py +0 -0
  90. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/config.py +0 -0
  91. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/engine_bridge.py +0 -0
  92. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/governance.py +0 -0
  93. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/main.py +0 -0
  94. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/multi_upstream.py +0 -0
  95. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/proxy/state.py +0 -0
  96. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/py.typed +0 -0
  97. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/sdk/__init__.py +0 -0
  98. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/sdk/_compat.py +0 -0
  99. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/sdk/compliance_kit.py +0 -0
  100. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/sdk/governed_agent.py +0 -0
  101. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/sdk/governed_data.py +0 -0
  102. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina/sdk/governed_model.py +0 -0
  103. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina_framework.egg-info/SOURCES.txt +0 -0
  104. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina_framework.egg-info/dependency_links.txt +0 -0
  105. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina_framework.egg-info/entry_points.txt +0 -0
  106. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina_framework.egg-info/requires.txt +2 -2
  107. {admina_framework-0.9.0 → admina_framework-0.9.2}/admina_framework.egg-info/top_level.txt +0 -0
  108. {admina_framework-0.9.0 → admina_framework-0.9.2}/setup.cfg +0 -0
  109. {admina_framework-0.9.0 → admina_framework-0.9.2}/tests/test_benchmark_14us.py +0 -0
  110. {admina_framework-0.9.0 → admina_framework-0.9.2}/tests/test_core_config.py +0 -0
  111. {admina_framework-0.9.0 → admina_framework-0.9.2}/tests/test_governance_pipeline.py +0 -0
  112. {admina_framework-0.9.0 → admina_framework-0.9.2}/tests/test_mcp_transport.py +0 -0
  113. {admina_framework-0.9.0 → admina_framework-0.9.2}/tests/test_proxy_security.py +0 -0
  114. {admina_framework-0.9.0 → admina_framework-0.9.2}/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.0
3
+ Version: 0.9.2
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"
@@ -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.0"
26
+ __version__ = "0.9.2"
27
27
 
28
28
  __all__ = [
29
29
  "__version__",
@@ -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.0
3
+ Version: 0.9.2
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"
@@ -19,7 +19,7 @@ build-backend = "setuptools.build_meta"
19
19
 
20
20
  [project]
21
21
  name = "admina-framework"
22
- version = "0.9.0"
22
+ version = "0.9.2"
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