admina-framework 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. admina/__init__.py +34 -0
  2. admina/cli/__init__.py +14 -0
  3. admina/cli/commands/__init__.py +14 -0
  4. admina/cli/main.py +1522 -0
  5. admina/cli/templates/admina.yaml.j2 +77 -0
  6. admina/cli/templates/docker-compose.yml.j2 +254 -0
  7. admina/cli/templates/env.j2 +10 -0
  8. admina/cli/templates/main.py.j2 +95 -0
  9. admina/cli/templates/plugin.py.j2 +145 -0
  10. admina/cli/templates/plugin_pyproject.toml.j2 +15 -0
  11. admina/cli/templates/plugin_readme.md.j2 +27 -0
  12. admina/cli/templates/plugin_test.py.j2 +48 -0
  13. admina/core/__init__.py +14 -0
  14. admina/core/config.py +497 -0
  15. admina/core/event_bus.py +112 -0
  16. admina/core/secrets.py +257 -0
  17. admina/core/types.py +146 -0
  18. admina/dashboard/__init__.py +8 -0
  19. admina/dashboard/static/heimdall.png +0 -0
  20. admina/dashboard/static/index.html +1045 -0
  21. admina/dashboard/static/vendor/alpinejs.min.js +5 -0
  22. admina/domains/__init__.py +14 -0
  23. admina/domains/agent_security/__init__.py +41 -0
  24. admina/domains/agent_security/firewall.py +634 -0
  25. admina/domains/agent_security/loop_breaker.py +176 -0
  26. admina/domains/ai_infra/__init__.py +79 -0
  27. admina/domains/ai_infra/llm_engine.py +477 -0
  28. admina/domains/ai_infra/rag.py +817 -0
  29. admina/domains/ai_infra/webui.py +292 -0
  30. admina/domains/compliance/__init__.py +109 -0
  31. admina/domains/compliance/cross_regulation.py +314 -0
  32. admina/domains/compliance/eu_ai_act.py +367 -0
  33. admina/domains/compliance/forensic.py +380 -0
  34. admina/domains/compliance/gdpr.py +331 -0
  35. admina/domains/compliance/nis2.py +258 -0
  36. admina/domains/compliance/oisg.py +658 -0
  37. admina/domains/compliance/otel.py +101 -0
  38. admina/domains/data_sovereignty/__init__.py +42 -0
  39. admina/domains/data_sovereignty/classification.py +102 -0
  40. admina/domains/data_sovereignty/pii.py +260 -0
  41. admina/domains/data_sovereignty/residency.py +121 -0
  42. admina/integrations/__init__.py +14 -0
  43. admina/integrations/_engines.py +63 -0
  44. admina/integrations/cheshirecat/__init__.py +13 -0
  45. admina/integrations/cheshirecat/admina-plugin/admina_governance.py +207 -0
  46. admina/integrations/crewai/__init__.py +13 -0
  47. admina/integrations/crewai/callbacks.py +347 -0
  48. admina/integrations/langchain/__init__.py +13 -0
  49. admina/integrations/langchain/callbacks.py +341 -0
  50. admina/integrations/n8n/__init__.py +14 -0
  51. admina/integrations/openclaw/__init__.py +14 -0
  52. admina/plugins/__init__.py +49 -0
  53. admina/plugins/base.py +633 -0
  54. admina/plugins/builtin/__init__.py +14 -0
  55. admina/plugins/builtin/adapters/__init__.py +14 -0
  56. admina/plugins/builtin/adapters/ollama.py +120 -0
  57. admina/plugins/builtin/adapters/openai.py +138 -0
  58. admina/plugins/builtin/alerts/__init__.py +14 -0
  59. admina/plugins/builtin/alerts/log.py +66 -0
  60. admina/plugins/builtin/alerts/webhook.py +102 -0
  61. admina/plugins/builtin/auth/__init__.py +14 -0
  62. admina/plugins/builtin/auth/apikey.py +138 -0
  63. admina/plugins/builtin/compliance/__init__.py +14 -0
  64. admina/plugins/builtin/compliance/eu_ai_act.py +202 -0
  65. admina/plugins/builtin/connectors/__init__.py +14 -0
  66. admina/plugins/builtin/connectors/chromadb.py +137 -0
  67. admina/plugins/builtin/connectors/filesystem.py +111 -0
  68. admina/plugins/builtin/forensic/__init__.py +14 -0
  69. admina/plugins/builtin/forensic/filesystem.py +163 -0
  70. admina/plugins/builtin/forensic/minio.py +180 -0
  71. admina/plugins/builtin/guards/__init__.py +0 -0
  72. admina/plugins/builtin/guards/guardrailsai_guard.py +172 -0
  73. admina/plugins/builtin/pii/__init__.py +14 -0
  74. admina/plugins/builtin/pii/spacy_regex.py +160 -0
  75. admina/plugins/builtin/transports/__init__.py +14 -0
  76. admina/plugins/builtin/transports/http_rest.py +97 -0
  77. admina/plugins/builtin/transports/mcp.py +173 -0
  78. admina/plugins/registry.py +356 -0
  79. admina/proxy/__init__.py +15 -0
  80. admina/proxy/api/__init__.py +17 -0
  81. admina/proxy/api/dashboard.py +925 -0
  82. admina/proxy/api/integration.py +153 -0
  83. admina/proxy/config.py +214 -0
  84. admina/proxy/engine_bridge.py +306 -0
  85. admina/proxy/governance.py +232 -0
  86. admina/proxy/main.py +1484 -0
  87. admina/proxy/multi_upstream.py +156 -0
  88. admina/proxy/state.py +97 -0
  89. admina/py.typed +0 -0
  90. admina/sdk/__init__.py +34 -0
  91. admina/sdk/_compat.py +43 -0
  92. admina/sdk/compliance_kit.py +359 -0
  93. admina/sdk/governed_agent.py +391 -0
  94. admina/sdk/governed_data.py +434 -0
  95. admina/sdk/governed_model.py +241 -0
  96. admina_framework-0.9.0.dist-info/METADATA +575 -0
  97. admina_framework-0.9.0.dist-info/RECORD +102 -0
  98. admina_framework-0.9.0.dist-info/WHEEL +5 -0
  99. admina_framework-0.9.0.dist-info/entry_points.txt +2 -0
  100. admina_framework-0.9.0.dist-info/licenses/LICENSE +191 -0
  101. admina_framework-0.9.0.dist-info/licenses/NOTICE +16 -0
  102. admina_framework-0.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,658 @@
1
+ # Copyright © 2025–2026 Stefano Noferi & Admina contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Admina — OISG Adequacy Score.
16
+
17
+ Implements the OISG (Open Intelligent Secure Governed) adequacy test
18
+ as defined at https://oisg.ai. The score is computed automatically by
19
+ inspecting the live runtime state of the Admina proxy — no manual
20
+ checkboxes required.
21
+
22
+ Each of the four pillars has 5 criteria worth 5 points each, for a
23
+ total score of 0–100.
24
+
25
+ Score levels:
26
+ 0–24 Critical gaps
27
+ 25–49 Partial coverage
28
+ 50–79 Good coverage
29
+ 80–100 OISG adequate
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import logging
35
+ from dataclasses import dataclass, field
36
+ from datetime import UTC, datetime
37
+ from typing import Any
38
+
39
+ logger = logging.getLogger("admina.compliance.oisg")
40
+
41
+ # ── Pillar colours (matching oisg.ai design system) ─────────
42
+ PILLAR_COLORS = {
43
+ "open": {"light": "#0F6E56", "dark": "#5DCAA5"},
44
+ "intelligent": {"light": "#534AB7", "dark": "#AFA9EC"},
45
+ "secure": {"light": "#993C1D", "dark": "#F0997B"},
46
+ "governed": {"light": "#185FA5", "dark": "#85B7EB"},
47
+ }
48
+
49
+ POINTS_PER_CRITERION = 5
50
+ CRITERIA_PER_PILLAR = 5
51
+ MAX_PILLAR_SCORE = POINTS_PER_CRITERION * CRITERIA_PER_PILLAR # 25
52
+ MAX_TOTAL_SCORE = MAX_PILLAR_SCORE * 4 # 100
53
+
54
+
55
+ # ── Criteria definitions ────────────────────────────────────
56
+
57
+ CRITERIA: dict[str, list[dict[str, str]]] = {
58
+ "open": [
59
+ {
60
+ "id": "o1",
61
+ "label": "Model documentation (capabilities, limitations, provenance) "
62
+ "is available to independent auditors",
63
+ },
64
+ {
65
+ "id": "o2",
66
+ "label": "Governance infrastructure (policy engines, decision logic) "
67
+ "is open and auditable",
68
+ },
69
+ {
70
+ "id": "o3",
71
+ "label": "Communication protocols use open standards (MCP, OpenTelemetry, A2A)",
72
+ },
73
+ {
74
+ "id": "o4",
75
+ "label": "Open projects have community stewardship "
76
+ "(contribution process, security disclosure, governance)",
77
+ },
78
+ {
79
+ "id": "o5",
80
+ "label": "Model provenance and training methodology are documented and reproducible",
81
+ },
82
+ ],
83
+ "intelligent": [
84
+ {
85
+ "id": "i1",
86
+ "label": "Model capabilities are measured with benchmark results, "
87
+ "known failure modes, and confidence calibration",
88
+ },
89
+ {
90
+ "id": "i2",
91
+ "label": "Infrastructure supports sovereign execution "
92
+ "(on-premise, private cloud, air-gapped) where required",
93
+ },
94
+ {
95
+ "id": "i3",
96
+ "label": "RAG pipelines are traceable "
97
+ "(document version, embedding model, retrieval path)",
98
+ },
99
+ {
100
+ "id": "i4",
101
+ "label": "Agent autonomy scope is explicit, machine-readable, and enforced at runtime",
102
+ },
103
+ {
104
+ "id": "i5",
105
+ "label": "System can produce on demand a complete explanation "
106
+ "of why it gave a specific response",
107
+ },
108
+ ],
109
+ "secure": [
110
+ {
111
+ "id": "s1",
112
+ "label": "Bidirectional injection defence operates on both request and response paths",
113
+ },
114
+ {
115
+ "id": "s2",
116
+ "label": "Agent identities are cryptographically verifiable (DIDs, Ed25519 key pairs)",
117
+ },
118
+ {
119
+ "id": "s3",
120
+ "label": "Transactional kill switch preserves forensic state and enables rollback",
121
+ },
122
+ {
123
+ "id": "s4",
124
+ "label": "PII redaction is enforced at infrastructure level before model endpoints",
125
+ },
126
+ {
127
+ "id": "s5",
128
+ "label": "Model supply chain integrity is verified "
129
+ "(fingerprinting, SBOM, cryptographic provenance)",
130
+ },
131
+ ],
132
+ "governed": [
133
+ {
134
+ "id": "g1",
135
+ "label": "Compliance is verified automatically at runtime, not through periodic audits",
136
+ },
137
+ {
138
+ "id": "g2",
139
+ "label": "Immutable forensic log (hash-chained) records all interactions and decisions",
140
+ },
141
+ {
142
+ "id": "g3",
143
+ "label": "Human oversight is architecturally defined "
144
+ "(which decisions, what info, what timeout)",
145
+ },
146
+ {
147
+ "id": "g4",
148
+ "label": "End-to-end observability is in place (distributed tracing, SLOs, dashboards)",
149
+ },
150
+ {
151
+ "id": "g5",
152
+ "label": "Risk classification is proportional, automated, "
153
+ "and auditable as capabilities evolve",
154
+ },
155
+ ],
156
+ }
157
+
158
+
159
+ @dataclass
160
+ class CriterionResult:
161
+ """Result of evaluating a single OISG criterion."""
162
+
163
+ id: str
164
+ label: str
165
+ satisfied: bool
166
+ reason: str = ""
167
+
168
+
169
+ @dataclass
170
+ class PillarResult:
171
+ """Score for one OISG pillar."""
172
+
173
+ name: str
174
+ score: int # 0–25
175
+ max_score: int = MAX_PILLAR_SCORE
176
+ criteria: list[CriterionResult] = field(default_factory=list)
177
+
178
+
179
+ @dataclass
180
+ class OISGResult:
181
+ """Full OISG adequacy score."""
182
+
183
+ total: int # 0–100
184
+ max_total: int = MAX_TOTAL_SCORE
185
+ level: str = ""
186
+ pillars: dict[str, PillarResult] = field(default_factory=dict)
187
+ computed_at: str = ""
188
+
189
+ def to_dict(self) -> dict[str, Any]:
190
+ return {
191
+ "total": self.total,
192
+ "max_total": self.max_total,
193
+ "level": self.level,
194
+ "pillars": {
195
+ name: {
196
+ "name": p.name,
197
+ "score": p.score,
198
+ "max_score": p.max_score,
199
+ "criteria": [
200
+ {
201
+ "id": c.id,
202
+ "label": c.label,
203
+ "satisfied": c.satisfied,
204
+ "reason": c.reason,
205
+ }
206
+ for c in p.criteria
207
+ ],
208
+ }
209
+ for name, p in self.pillars.items()
210
+ },
211
+ "computed_at": self.computed_at,
212
+ }
213
+
214
+
215
+ def get_level(total: int) -> str:
216
+ """Classify total score into an OISG adequacy level."""
217
+ if total >= 80:
218
+ return "OISG adequate"
219
+ if total >= 50:
220
+ return "Good coverage"
221
+ if total >= 25:
222
+ return "Partial coverage"
223
+ return "Critical gaps"
224
+
225
+
226
+ def compute_oisg_score(
227
+ *,
228
+ firewall: Any | None = None,
229
+ pii_redactor: Any | None = None,
230
+ loop_breaker: Any | None = None,
231
+ forensic_box: Any | None = None,
232
+ compliance_engine: Any | None = None,
233
+ otel_exporter: Any | None = None,
234
+ governance_guards: list | None = None,
235
+ config: Any | None = None,
236
+ engine_status: dict[str, Any] | None = None,
237
+ metrics: dict[str, Any] | None = None,
238
+ ) -> OISGResult:
239
+ """Compute the OISG adequacy score from Admina's live runtime state.
240
+
241
+ Each criterion is evaluated by inspecting whether the corresponding
242
+ Admina subsystem is active and properly configured.
243
+ """
244
+ if governance_guards is None:
245
+ governance_guards = []
246
+ if engine_status is None:
247
+ engine_status = {}
248
+ if metrics is None:
249
+ metrics = {}
250
+
251
+ pillars: dict[str, PillarResult] = {}
252
+
253
+ # ── O — Open ────────────────────────────────────────────
254
+ o_criteria = _evaluate_open(
255
+ config=config,
256
+ otel_exporter=otel_exporter,
257
+ engine_status=engine_status,
258
+ )
259
+ pillars["open"] = _build_pillar("Open", o_criteria)
260
+
261
+ # ── I — Intelligent ─────────────────────────────────────
262
+ i_criteria = _evaluate_intelligent(
263
+ config=config,
264
+ firewall=firewall,
265
+ loop_breaker=loop_breaker,
266
+ governance_guards=governance_guards,
267
+ forensic_box=forensic_box,
268
+ )
269
+ pillars["intelligent"] = _build_pillar("Intelligent", i_criteria)
270
+
271
+ # ── S — Secure ──────────────────────────────────────────
272
+ s_criteria = _evaluate_secure(
273
+ firewall=firewall,
274
+ pii_redactor=pii_redactor,
275
+ forensic_box=forensic_box,
276
+ config=config,
277
+ engine_status=engine_status,
278
+ )
279
+ pillars["secure"] = _build_pillar("Secure", s_criteria)
280
+
281
+ # ── G — Governed ────────────────────────────────────────
282
+ g_criteria = _evaluate_governed(
283
+ compliance_engine=compliance_engine,
284
+ forensic_box=forensic_box,
285
+ otel_exporter=otel_exporter,
286
+ governance_guards=governance_guards,
287
+ config=config,
288
+ )
289
+ pillars["governed"] = _build_pillar("Governed", g_criteria)
290
+
291
+ total = sum(p.score for p in pillars.values())
292
+ return OISGResult(
293
+ total=total,
294
+ level=get_level(total),
295
+ pillars=pillars,
296
+ computed_at=datetime.now(UTC).isoformat(),
297
+ )
298
+
299
+
300
+ # ── Pillar evaluation helpers ───────────────────────────────
301
+
302
+
303
+ def _build_pillar(name: str, criteria: list[CriterionResult]) -> PillarResult:
304
+ score = sum(POINTS_PER_CRITERION for c in criteria if c.satisfied)
305
+ return PillarResult(name=name, score=score, criteria=criteria)
306
+
307
+
308
+ def _evaluate_open(
309
+ *,
310
+ config: Any | None,
311
+ otel_exporter: Any | None,
312
+ engine_status: dict[str, Any],
313
+ ) -> list[CriterionResult]:
314
+ defs = CRITERIA["open"]
315
+ results: list[CriterionResult] = []
316
+
317
+ # O1: Model documentation available — satisfied if ai_infra is
318
+ # enabled (model endpoints expose capabilities)
319
+ ai_enabled = False
320
+ if config is not None:
321
+ ai_cfg = getattr(config, "ai_infra", None)
322
+ if ai_cfg is not None:
323
+ ai_enabled = getattr(ai_cfg, "enabled", False)
324
+ results.append(
325
+ CriterionResult(
326
+ id=defs[0]["id"],
327
+ label=defs[0]["label"],
328
+ satisfied=ai_enabled,
329
+ reason="AI infra enabled — model info endpoint active"
330
+ if ai_enabled
331
+ else "AI infra not enabled — no model documentation exposed",
332
+ )
333
+ )
334
+
335
+ # O2: Governance infrastructure open and auditable — always true
336
+ # (Admina is Apache 2.0 open source)
337
+ results.append(
338
+ CriterionResult(
339
+ id=defs[1]["id"],
340
+ label=defs[1]["label"],
341
+ satisfied=True,
342
+ reason="Admina governance engine is open source (Apache 2.0)",
343
+ )
344
+ )
345
+
346
+ # O3: Open standards (MCP, OpenTelemetry, A2A) — satisfied when
347
+ # the proxy is running (always MCP) and OTEL is configured
348
+ has_otel = otel_exporter is not None
349
+ results.append(
350
+ CriterionResult(
351
+ id=defs[2]["id"],
352
+ label=defs[2]["label"],
353
+ satisfied=True, # proxy always speaks MCP
354
+ reason="Proxy uses MCP protocol"
355
+ + ("; OTEL exporter active" if has_otel else "; OTEL not configured"),
356
+ )
357
+ )
358
+
359
+ # O4: Community stewardship — always true (Apache 2.0, CONTRIBUTING,
360
+ # SECURITY.md)
361
+ results.append(
362
+ CriterionResult(
363
+ id=defs[3]["id"],
364
+ label=defs[3]["label"],
365
+ satisfied=True,
366
+ reason="Apache 2.0 license, contribution process, security disclosure policy",
367
+ )
368
+ )
369
+
370
+ # O5: Model provenance documented — satisfied if engine status
371
+ # reports version info
372
+ has_version = bool(engine_status.get("rust_version") or engine_status.get("version"))
373
+ results.append(
374
+ CriterionResult(
375
+ id=defs[4]["id"],
376
+ label=defs[4]["label"],
377
+ satisfied=has_version,
378
+ reason="Engine version tracked"
379
+ if has_version
380
+ else "No engine version information available",
381
+ )
382
+ )
383
+
384
+ return results
385
+
386
+
387
+ def _evaluate_intelligent(
388
+ *,
389
+ config: Any | None,
390
+ firewall: Any | None,
391
+ loop_breaker: Any | None,
392
+ governance_guards: list,
393
+ forensic_box: Any | None,
394
+ ) -> list[CriterionResult]:
395
+ defs = CRITERIA["intelligent"]
396
+ results: list[CriterionResult] = []
397
+
398
+ # I1: Capabilities measured — satisfied if governance engine
399
+ # benchmarks exist (engine is benchmarked in CI)
400
+ # We check for the presence of firewall + loop_breaker as
401
+ # evidence of measured, calibrated detection thresholds.
402
+ has_calibrated = firewall is not None and loop_breaker is not None
403
+ results.append(
404
+ CriterionResult(
405
+ id=defs[0]["id"],
406
+ label=defs[0]["label"],
407
+ satisfied=has_calibrated,
408
+ reason="Firewall and loop breaker active with calibrated thresholds"
409
+ if has_calibrated
410
+ else "Detection engines not fully configured",
411
+ )
412
+ )
413
+
414
+ # I2: Sovereign execution — always true (Admina runs entirely
415
+ # on-premise, no cloud dependency)
416
+ results.append(
417
+ CriterionResult(
418
+ id=defs[1]["id"],
419
+ label=defs[1]["label"],
420
+ satisfied=True,
421
+ reason="Admina runs entirely on-premise — sovereign by design",
422
+ )
423
+ )
424
+
425
+ # I3: RAG pipelines traceable — satisfied if RAG is enabled
426
+ rag_enabled = False
427
+ if config is not None:
428
+ ai_cfg = getattr(config, "ai_infra", None)
429
+ if ai_cfg is not None and getattr(ai_cfg, "enabled", False):
430
+ rag_cfg = getattr(ai_cfg, "rag", None)
431
+ if rag_cfg is not None:
432
+ rag_enabled = getattr(rag_cfg, "enabled", False)
433
+ results.append(
434
+ CriterionResult(
435
+ id=defs[2]["id"],
436
+ label=defs[2]["label"],
437
+ satisfied=rag_enabled,
438
+ reason="RAG pipeline enabled with traceable config"
439
+ if rag_enabled
440
+ else "RAG pipeline not enabled",
441
+ )
442
+ )
443
+
444
+ # I4: Agent autonomy enforced at runtime — satisfied if
445
+ # governance pipeline is active (firewall + guards)
446
+ # The governance pipeline itself enforces bounded autonomy
447
+ results.append(
448
+ CriterionResult(
449
+ id=defs[3]["id"],
450
+ label=defs[3]["label"],
451
+ satisfied=firewall is not None,
452
+ reason="Governance pipeline enforces agent autonomy scope"
453
+ if firewall is not None
454
+ else "Governance pipeline not active",
455
+ )
456
+ )
457
+
458
+ # I5: Explainability on demand — satisfied if forensic box is
459
+ # active (complete decision trace available)
460
+ has_explainability = forensic_box is not None
461
+ results.append(
462
+ CriterionResult(
463
+ id=defs[4]["id"],
464
+ label=defs[4]["label"],
465
+ satisfied=has_explainability,
466
+ reason="Forensic black box provides full decision trace"
467
+ if has_explainability
468
+ else "No forensic box — explainability not available",
469
+ )
470
+ )
471
+
472
+ return results
473
+
474
+
475
+ def _evaluate_secure(
476
+ *,
477
+ firewall: Any | None,
478
+ pii_redactor: Any | None,
479
+ forensic_box: Any | None,
480
+ config: Any | None,
481
+ engine_status: dict[str, Any],
482
+ ) -> list[CriterionResult]:
483
+ defs = CRITERIA["secure"]
484
+ results: list[CriterionResult] = []
485
+
486
+ # S1: Bidirectional injection defence — satisfied if firewall
487
+ # is active (scans both inbound and outbound in pipeline)
488
+ fw_active = firewall is not None
489
+ results.append(
490
+ CriterionResult(
491
+ id=defs[0]["id"],
492
+ label=defs[0]["label"],
493
+ satisfied=fw_active,
494
+ reason="Anti-injection firewall active on request path"
495
+ if fw_active
496
+ else "Firewall not configured",
497
+ )
498
+ )
499
+
500
+ # S2: Cryptographic agent identity — satisfied if API key auth
501
+ # is configured (first step toward cryptographic identity)
502
+ has_auth = False
503
+ if config is not None:
504
+ api_key = getattr(config, "admina_api_key", "")
505
+ has_auth = bool(api_key)
506
+ results.append(
507
+ CriterionResult(
508
+ id=defs[1]["id"],
509
+ label=defs[1]["label"],
510
+ satisfied=has_auth,
511
+ reason="API key authentication configured"
512
+ if has_auth
513
+ else "No agent authentication configured",
514
+ )
515
+ )
516
+
517
+ # S3: Kill switch preserves forensic state — satisfied if
518
+ # circuit_break action exists and forensic box is active
519
+ has_kill_switch = forensic_box is not None
520
+ results.append(
521
+ CriterionResult(
522
+ id=defs[2]["id"],
523
+ label=defs[2]["label"],
524
+ satisfied=has_kill_switch,
525
+ reason="Circuit breaker + forensic black box active"
526
+ if has_kill_switch
527
+ else "No forensic box for kill switch state preservation",
528
+ )
529
+ )
530
+
531
+ # S4: PII redaction at infrastructure level — satisfied if
532
+ # PII redactor is enabled
533
+ pii_active = pii_redactor is not None
534
+ results.append(
535
+ CriterionResult(
536
+ id=defs[3]["id"],
537
+ label=defs[3]["label"],
538
+ satisfied=pii_active,
539
+ reason="PII redaction enforced at proxy level"
540
+ if pii_active
541
+ else "PII redaction not configured",
542
+ )
543
+ )
544
+
545
+ # S5: Model supply chain integrity — satisfied if Rust engine
546
+ # is active (versioned, reproducible builds)
547
+ has_integrity = engine_status.get("rust_available", False)
548
+ results.append(
549
+ CriterionResult(
550
+ id=defs[4]["id"],
551
+ label=defs[4]["label"],
552
+ satisfied=has_integrity,
553
+ reason="Rust engine with versioned builds"
554
+ if has_integrity
555
+ else "No verifiable engine build information",
556
+ )
557
+ )
558
+
559
+ return results
560
+
561
+
562
+ def _evaluate_governed(
563
+ *,
564
+ compliance_engine: Any | None,
565
+ forensic_box: Any | None,
566
+ otel_exporter: Any | None,
567
+ governance_guards: list,
568
+ config: Any | None,
569
+ ) -> list[CriterionResult]:
570
+ defs = CRITERIA["governed"]
571
+ results: list[CriterionResult] = []
572
+
573
+ # G1: Runtime compliance verification — satisfied if EU AI Act
574
+ # compliance engine is active
575
+ compliance_active = compliance_engine is not None
576
+ eu_enabled = True
577
+ if config is not None:
578
+ comp_cfg = getattr(config, "compliance", None)
579
+ if comp_cfg is not None:
580
+ eu_enabled = getattr(comp_cfg, "eu_ai_act_enabled", True)
581
+ g1_ok = compliance_active and eu_enabled
582
+ results.append(
583
+ CriterionResult(
584
+ id=defs[0]["id"],
585
+ label=defs[0]["label"],
586
+ satisfied=g1_ok,
587
+ reason="EU AI Act compliance engine active"
588
+ if g1_ok
589
+ else "Compliance engine not active",
590
+ )
591
+ )
592
+
593
+ # G2: Immutable forensic log — satisfied if forensic black box
594
+ # is active with valid chain
595
+ chain_valid = (
596
+ forensic_box is not None and getattr(forensic_box, "chain_head", "GENESIS") != "GENESIS"
597
+ )
598
+ results.append(
599
+ CriterionResult(
600
+ id=defs[1]["id"],
601
+ label=defs[1]["label"],
602
+ satisfied=forensic_box is not None,
603
+ reason="SHA-256 hash-chained forensic black box active"
604
+ + (" (chain initialised)" if chain_valid else " (chain at GENESIS)")
605
+ if forensic_box is not None
606
+ else "Forensic black box not configured",
607
+ )
608
+ )
609
+
610
+ # G3: Human oversight architecturally defined — satisfied if
611
+ # governance guards or escalation policies are configured
612
+ has_oversight = len(governance_guards) > 0
613
+ results.append(
614
+ CriterionResult(
615
+ id=defs[2]["id"],
616
+ label=defs[2]["label"],
617
+ satisfied=has_oversight,
618
+ reason=f"{len(governance_guards)} governance guard(s) enforce oversight"
619
+ if has_oversight
620
+ else "No governance guards configured for human oversight",
621
+ )
622
+ )
623
+
624
+ # G4: End-to-end observability — satisfied if OTEL exporter
625
+ # and dashboard are configured
626
+ has_otel = otel_exporter is not None
627
+ dashboard_enabled = True
628
+ if config is not None:
629
+ dash_cfg = getattr(config, "dashboard", None)
630
+ if dash_cfg is not None:
631
+ dashboard_enabled = getattr(dash_cfg, "enabled", True)
632
+ g4_ok = has_otel or dashboard_enabled
633
+ results.append(
634
+ CriterionResult(
635
+ id=defs[3]["id"],
636
+ label=defs[3]["label"],
637
+ satisfied=g4_ok,
638
+ reason="Observability active"
639
+ + (" (OTEL" if has_otel else " (no OTEL")
640
+ + (", dashboard enabled)" if dashboard_enabled else ", no dashboard)"),
641
+ )
642
+ )
643
+
644
+ # G5: Risk classification automated — satisfied if compliance
645
+ # engine can classify risk
646
+ has_classification = compliance_active and hasattr(compliance_engine, "classify_risk")
647
+ results.append(
648
+ CriterionResult(
649
+ id=defs[4]["id"],
650
+ label=defs[4]["label"],
651
+ satisfied=has_classification,
652
+ reason="Automated risk classification via EU AI Act engine"
653
+ if has_classification
654
+ else "No automated risk classification available",
655
+ )
656
+ )
657
+
658
+ return results