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.
- admina/__init__.py +34 -0
- admina/cli/__init__.py +14 -0
- admina/cli/commands/__init__.py +14 -0
- admina/cli/main.py +1522 -0
- admina/cli/templates/admina.yaml.j2 +77 -0
- admina/cli/templates/docker-compose.yml.j2 +254 -0
- admina/cli/templates/env.j2 +10 -0
- admina/cli/templates/main.py.j2 +95 -0
- admina/cli/templates/plugin.py.j2 +145 -0
- admina/cli/templates/plugin_pyproject.toml.j2 +15 -0
- admina/cli/templates/plugin_readme.md.j2 +27 -0
- admina/cli/templates/plugin_test.py.j2 +48 -0
- admina/core/__init__.py +14 -0
- admina/core/config.py +497 -0
- admina/core/event_bus.py +112 -0
- admina/core/secrets.py +257 -0
- admina/core/types.py +146 -0
- admina/dashboard/__init__.py +8 -0
- admina/dashboard/static/heimdall.png +0 -0
- admina/dashboard/static/index.html +1045 -0
- admina/dashboard/static/vendor/alpinejs.min.js +5 -0
- admina/domains/__init__.py +14 -0
- admina/domains/agent_security/__init__.py +41 -0
- admina/domains/agent_security/firewall.py +634 -0
- admina/domains/agent_security/loop_breaker.py +176 -0
- admina/domains/ai_infra/__init__.py +79 -0
- admina/domains/ai_infra/llm_engine.py +477 -0
- admina/domains/ai_infra/rag.py +817 -0
- admina/domains/ai_infra/webui.py +292 -0
- admina/domains/compliance/__init__.py +109 -0
- admina/domains/compliance/cross_regulation.py +314 -0
- admina/domains/compliance/eu_ai_act.py +367 -0
- admina/domains/compliance/forensic.py +380 -0
- admina/domains/compliance/gdpr.py +331 -0
- admina/domains/compliance/nis2.py +258 -0
- admina/domains/compliance/oisg.py +658 -0
- admina/domains/compliance/otel.py +101 -0
- admina/domains/data_sovereignty/__init__.py +42 -0
- admina/domains/data_sovereignty/classification.py +102 -0
- admina/domains/data_sovereignty/pii.py +260 -0
- admina/domains/data_sovereignty/residency.py +121 -0
- admina/integrations/__init__.py +14 -0
- admina/integrations/_engines.py +63 -0
- admina/integrations/cheshirecat/__init__.py +13 -0
- admina/integrations/cheshirecat/admina-plugin/admina_governance.py +207 -0
- admina/integrations/crewai/__init__.py +13 -0
- admina/integrations/crewai/callbacks.py +347 -0
- admina/integrations/langchain/__init__.py +13 -0
- admina/integrations/langchain/callbacks.py +341 -0
- admina/integrations/n8n/__init__.py +14 -0
- admina/integrations/openclaw/__init__.py +14 -0
- admina/plugins/__init__.py +49 -0
- admina/plugins/base.py +633 -0
- admina/plugins/builtin/__init__.py +14 -0
- admina/plugins/builtin/adapters/__init__.py +14 -0
- admina/plugins/builtin/adapters/ollama.py +120 -0
- admina/plugins/builtin/adapters/openai.py +138 -0
- admina/plugins/builtin/alerts/__init__.py +14 -0
- admina/plugins/builtin/alerts/log.py +66 -0
- admina/plugins/builtin/alerts/webhook.py +102 -0
- admina/plugins/builtin/auth/__init__.py +14 -0
- admina/plugins/builtin/auth/apikey.py +138 -0
- admina/plugins/builtin/compliance/__init__.py +14 -0
- admina/plugins/builtin/compliance/eu_ai_act.py +202 -0
- admina/plugins/builtin/connectors/__init__.py +14 -0
- admina/plugins/builtin/connectors/chromadb.py +137 -0
- admina/plugins/builtin/connectors/filesystem.py +111 -0
- admina/plugins/builtin/forensic/__init__.py +14 -0
- admina/plugins/builtin/forensic/filesystem.py +163 -0
- admina/plugins/builtin/forensic/minio.py +180 -0
- admina/plugins/builtin/guards/__init__.py +0 -0
- admina/plugins/builtin/guards/guardrailsai_guard.py +172 -0
- admina/plugins/builtin/pii/__init__.py +14 -0
- admina/plugins/builtin/pii/spacy_regex.py +160 -0
- admina/plugins/builtin/transports/__init__.py +14 -0
- admina/plugins/builtin/transports/http_rest.py +97 -0
- admina/plugins/builtin/transports/mcp.py +173 -0
- admina/plugins/registry.py +356 -0
- admina/proxy/__init__.py +15 -0
- admina/proxy/api/__init__.py +17 -0
- admina/proxy/api/dashboard.py +925 -0
- admina/proxy/api/integration.py +153 -0
- admina/proxy/config.py +214 -0
- admina/proxy/engine_bridge.py +306 -0
- admina/proxy/governance.py +232 -0
- admina/proxy/main.py +1484 -0
- admina/proxy/multi_upstream.py +156 -0
- admina/proxy/state.py +97 -0
- admina/py.typed +0 -0
- admina/sdk/__init__.py +34 -0
- admina/sdk/_compat.py +43 -0
- admina/sdk/compliance_kit.py +359 -0
- admina/sdk/governed_agent.py +391 -0
- admina/sdk/governed_data.py +434 -0
- admina/sdk/governed_model.py +241 -0
- admina_framework-0.9.0.dist-info/METADATA +575 -0
- admina_framework-0.9.0.dist-info/RECORD +102 -0
- admina_framework-0.9.0.dist-info/WHEEL +5 -0
- admina_framework-0.9.0.dist-info/entry_points.txt +2 -0
- admina_framework-0.9.0.dist-info/licenses/LICENSE +191 -0
- admina_framework-0.9.0.dist-info/licenses/NOTICE +16 -0
- admina_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,341 @@
|
|
|
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 governance callback handler for LangChain.
|
|
16
|
+
|
|
17
|
+
Provides :class:`AdminaCallbackHandler` — a LangChain ``BaseCallbackHandler``
|
|
18
|
+
that validates every LLM call, tool invocation, and chain output through
|
|
19
|
+
the Admina governance pipeline (firewall, PII redaction, loop detection).
|
|
20
|
+
|
|
21
|
+
Works **in-process** via the Admina SDK — no sidecar proxy needed.
|
|
22
|
+
|
|
23
|
+
Usage::
|
|
24
|
+
|
|
25
|
+
from langchain_openai import ChatOpenAI
|
|
26
|
+
from admina.integrations.langchain.callbacks import AdminaCallbackHandler
|
|
27
|
+
|
|
28
|
+
handler = AdminaCallbackHandler()
|
|
29
|
+
llm = ChatOpenAI(callbacks=[handler])
|
|
30
|
+
llm.invoke("Summarize this document")
|
|
31
|
+
|
|
32
|
+
# Check governance results
|
|
33
|
+
print(handler.last_result)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import logging
|
|
39
|
+
import time
|
|
40
|
+
import uuid
|
|
41
|
+
from dataclasses import dataclass, field
|
|
42
|
+
from typing import Any
|
|
43
|
+
|
|
44
|
+
from admina.core.event_bus import GovernanceEvent, bus
|
|
45
|
+
from admina.core.types import EventType
|
|
46
|
+
from admina.integrations._engines import get_firewall, get_loop_breaker, get_pii_redactor
|
|
47
|
+
from admina.sdk._compat import run_sync
|
|
48
|
+
|
|
49
|
+
logger = logging.getLogger("admina.integrations.langchain")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class GovernanceBlockedError(Exception):
|
|
53
|
+
"""Raised when Admina blocks a request due to governance policy."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, action: str, risk_level: str, details: dict | None = None):
|
|
56
|
+
self.action = action
|
|
57
|
+
self.risk_level = risk_level
|
|
58
|
+
self.details = details or {}
|
|
59
|
+
super().__init__(f"Admina governance {action}: risk_level={risk_level}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class GovernanceResult:
|
|
64
|
+
"""Result of an Admina governance check."""
|
|
65
|
+
|
|
66
|
+
action: str = "ALLOW"
|
|
67
|
+
risk_level: str = "LOW"
|
|
68
|
+
pii_count: int = 0
|
|
69
|
+
redacted_text: str | None = None
|
|
70
|
+
checks: dict[str, Any] = field(default_factory=dict)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AdminaCallbackHandler:
|
|
74
|
+
"""LangChain callback handler with Admina governance.
|
|
75
|
+
|
|
76
|
+
Intercepts LLM calls, tool invocations, and chain runs to
|
|
77
|
+
enforce PII redaction, injection firewall, and loop detection.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
session_id: Session identifier for loop detection across calls.
|
|
81
|
+
pii_redaction: Whether to redact PII from prompts/responses.
|
|
82
|
+
firewall: Whether to check for prompt injections.
|
|
83
|
+
loop_detection: Whether to detect reasoning loops.
|
|
84
|
+
on_block: Action when governance blocks a request:
|
|
85
|
+
``"raise"`` raises :class:`GovernanceBlockedError`,
|
|
86
|
+
``"warn"`` logs a warning and continues.
|
|
87
|
+
audit: Whether to emit events to the governance event bus.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
session_id: str | None = None,
|
|
93
|
+
pii_redaction: bool = True,
|
|
94
|
+
firewall: bool = True,
|
|
95
|
+
loop_detection: bool = True,
|
|
96
|
+
on_block: str = "raise",
|
|
97
|
+
audit: bool = True,
|
|
98
|
+
) -> None:
|
|
99
|
+
self.session_id = session_id or f"langchain-{uuid.uuid4().hex[:8]}"
|
|
100
|
+
self.pii_redaction = pii_redaction
|
|
101
|
+
self.firewall = firewall
|
|
102
|
+
self.loop_detection = loop_detection
|
|
103
|
+
self.on_block = on_block
|
|
104
|
+
self.audit = audit
|
|
105
|
+
|
|
106
|
+
self.last_result: GovernanceResult | None = None
|
|
107
|
+
self._call_count = 0
|
|
108
|
+
self._block_count = 0
|
|
109
|
+
self._redact_count = 0
|
|
110
|
+
|
|
111
|
+
# ── Internal governance check ────────────────────────────
|
|
112
|
+
|
|
113
|
+
def _govern(self, text: str, direction: str = "inbound") -> GovernanceResult:
|
|
114
|
+
"""Run governance checks on text and return result."""
|
|
115
|
+
result = GovernanceResult()
|
|
116
|
+
start = time.perf_counter()
|
|
117
|
+
|
|
118
|
+
# Firewall
|
|
119
|
+
if self.firewall and direction == "inbound":
|
|
120
|
+
fw = get_firewall()
|
|
121
|
+
fw_result = fw.check(text)
|
|
122
|
+
result.checks["firewall"] = fw_result
|
|
123
|
+
if fw_result.get("is_injection"):
|
|
124
|
+
result.action = "BLOCK"
|
|
125
|
+
rl = fw_result.get("risk_level", "HIGH")
|
|
126
|
+
result.risk_level = rl.value if hasattr(rl, "value") else str(rl)
|
|
127
|
+
|
|
128
|
+
# Loop detection
|
|
129
|
+
if self.loop_detection and direction == "inbound":
|
|
130
|
+
lb = get_loop_breaker()
|
|
131
|
+
lb_result = lb.check(self.session_id, text)
|
|
132
|
+
result.checks["loop_breaker"] = lb_result
|
|
133
|
+
if lb_result.get("is_loop"):
|
|
134
|
+
result.action = "CIRCUIT_BREAK"
|
|
135
|
+
result.risk_level = "HIGH"
|
|
136
|
+
|
|
137
|
+
# PII redaction
|
|
138
|
+
if self.pii_redaction:
|
|
139
|
+
pii = get_pii_redactor()
|
|
140
|
+
pii_result = pii.redact(text)
|
|
141
|
+
result.checks["pii_redaction"] = {
|
|
142
|
+
"count": pii_result["count"],
|
|
143
|
+
"entities": [e["type"] for e in pii_result.get("entities", [])],
|
|
144
|
+
}
|
|
145
|
+
if pii_result["count"] > 0:
|
|
146
|
+
result.pii_count = pii_result["count"]
|
|
147
|
+
result.redacted_text = pii_result["redacted_text"]
|
|
148
|
+
if result.action == "ALLOW":
|
|
149
|
+
result.action = "REDACT"
|
|
150
|
+
|
|
151
|
+
result.checks["latency_ms"] = round((time.perf_counter() - start) * 1000, 2)
|
|
152
|
+
self.last_result = result
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
def _handle_block(self, result: GovernanceResult, context: str) -> None:
|
|
156
|
+
"""Handle a BLOCK or CIRCUIT_BREAK governance decision."""
|
|
157
|
+
self._block_count += 1
|
|
158
|
+
if self.on_block == "raise":
|
|
159
|
+
raise GovernanceBlockedError(result.action, result.risk_level, result.checks)
|
|
160
|
+
logger.warning(
|
|
161
|
+
"[BLOCKED] %s: action=%s risk=%s",
|
|
162
|
+
context,
|
|
163
|
+
result.action,
|
|
164
|
+
result.risk_level,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def _emit(self, event_type: EventType, **kwargs: Any) -> None:
|
|
168
|
+
"""Emit a governance event to the bus."""
|
|
169
|
+
if not self.audit:
|
|
170
|
+
return
|
|
171
|
+
event = GovernanceEvent(
|
|
172
|
+
event_type=event_type,
|
|
173
|
+
session_id=self.session_id,
|
|
174
|
+
domain="langchain",
|
|
175
|
+
**kwargs,
|
|
176
|
+
)
|
|
177
|
+
run_sync(bus.emit(event))
|
|
178
|
+
|
|
179
|
+
# ── LangChain callback interface ─────────────────────────
|
|
180
|
+
# These methods match LangChain's BaseCallbackHandler protocol.
|
|
181
|
+
# We don't inherit from it to avoid requiring langchain as a
|
|
182
|
+
# dependency — LangChain checks for method presence via duck typing.
|
|
183
|
+
|
|
184
|
+
def on_llm_start(
|
|
185
|
+
self,
|
|
186
|
+
serialized: dict[str, Any],
|
|
187
|
+
prompts: list[str],
|
|
188
|
+
*,
|
|
189
|
+
run_id: Any = None,
|
|
190
|
+
**kwargs: Any,
|
|
191
|
+
) -> None:
|
|
192
|
+
"""Called when an LLM starts generating."""
|
|
193
|
+
self._call_count += 1
|
|
194
|
+
combined = "\n".join(prompts)
|
|
195
|
+
|
|
196
|
+
result = self._govern(combined, direction="inbound")
|
|
197
|
+
|
|
198
|
+
self._emit(
|
|
199
|
+
EventType.MODEL_CALL,
|
|
200
|
+
action=result.action,
|
|
201
|
+
risk_level=result.risk_level,
|
|
202
|
+
metadata={
|
|
203
|
+
"model": serialized.get("name", "unknown"),
|
|
204
|
+
"prompt_length": len(combined),
|
|
205
|
+
"pii_count": result.pii_count,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if result.action in ("BLOCK", "CIRCUIT_BREAK"):
|
|
210
|
+
self._handle_block(result, "on_llm_start")
|
|
211
|
+
|
|
212
|
+
def on_llm_end(
|
|
213
|
+
self,
|
|
214
|
+
response: Any,
|
|
215
|
+
*,
|
|
216
|
+
run_id: Any = None,
|
|
217
|
+
**kwargs: Any,
|
|
218
|
+
) -> None:
|
|
219
|
+
"""Called when an LLM finishes generating."""
|
|
220
|
+
# Extract text from LLMResult
|
|
221
|
+
text = ""
|
|
222
|
+
if hasattr(response, "generations") and response.generations:
|
|
223
|
+
gen = response.generations[0]
|
|
224
|
+
if gen:
|
|
225
|
+
text = gen[0].text if hasattr(gen[0], "text") else str(gen[0])
|
|
226
|
+
|
|
227
|
+
if text:
|
|
228
|
+
result = self._govern(text, direction="outbound")
|
|
229
|
+
if result.pii_count > 0:
|
|
230
|
+
self._redact_count += 1
|
|
231
|
+
|
|
232
|
+
self._emit(
|
|
233
|
+
EventType.MODEL_RESPONSE,
|
|
234
|
+
action=result.action,
|
|
235
|
+
risk_level=result.risk_level,
|
|
236
|
+
metadata={
|
|
237
|
+
"response_length": len(text),
|
|
238
|
+
"pii_redacted": result.pii_count,
|
|
239
|
+
},
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def on_llm_error(
|
|
243
|
+
self,
|
|
244
|
+
error: BaseException,
|
|
245
|
+
*,
|
|
246
|
+
run_id: Any = None,
|
|
247
|
+
**kwargs: Any,
|
|
248
|
+
) -> None:
|
|
249
|
+
"""Called when an LLM errors."""
|
|
250
|
+
logger.warning("LLM error in governed session %s: %s", self.session_id, error)
|
|
251
|
+
|
|
252
|
+
def on_tool_start(
|
|
253
|
+
self,
|
|
254
|
+
serialized: dict[str, Any],
|
|
255
|
+
input_str: str,
|
|
256
|
+
*,
|
|
257
|
+
run_id: Any = None,
|
|
258
|
+
**kwargs: Any,
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Called when a tool starts running."""
|
|
261
|
+
result = self._govern(input_str, direction="inbound")
|
|
262
|
+
|
|
263
|
+
self._emit(
|
|
264
|
+
EventType.AGENT_REQUEST,
|
|
265
|
+
action=result.action,
|
|
266
|
+
risk_level=result.risk_level,
|
|
267
|
+
metadata={
|
|
268
|
+
"tool": serialized.get("name", "unknown"),
|
|
269
|
+
"input_length": len(input_str),
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if result.action in ("BLOCK", "CIRCUIT_BREAK"):
|
|
274
|
+
self._handle_block(result, f"on_tool_start({serialized.get('name', '?')})")
|
|
275
|
+
|
|
276
|
+
def on_tool_end(
|
|
277
|
+
self,
|
|
278
|
+
output: str,
|
|
279
|
+
*,
|
|
280
|
+
run_id: Any = None,
|
|
281
|
+
**kwargs: Any,
|
|
282
|
+
) -> None:
|
|
283
|
+
"""Called when a tool finishes."""
|
|
284
|
+
if output:
|
|
285
|
+
result = self._govern(str(output), direction="outbound")
|
|
286
|
+
if result.pii_count > 0:
|
|
287
|
+
self._redact_count += 1
|
|
288
|
+
|
|
289
|
+
self._emit(
|
|
290
|
+
EventType.AGENT_RESPONSE,
|
|
291
|
+
action=result.action,
|
|
292
|
+
risk_level=result.risk_level,
|
|
293
|
+
metadata={"output_length": len(str(output)), "pii_redacted": result.pii_count},
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def on_chain_start(
|
|
297
|
+
self,
|
|
298
|
+
serialized: dict[str, Any],
|
|
299
|
+
inputs: dict[str, Any],
|
|
300
|
+
*,
|
|
301
|
+
run_id: Any = None,
|
|
302
|
+
**kwargs: Any,
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Called when a chain starts."""
|
|
305
|
+
pass # governance is applied at LLM/tool level
|
|
306
|
+
|
|
307
|
+
def on_chain_end(
|
|
308
|
+
self,
|
|
309
|
+
outputs: dict[str, Any],
|
|
310
|
+
*,
|
|
311
|
+
run_id: Any = None,
|
|
312
|
+
**kwargs: Any,
|
|
313
|
+
) -> None:
|
|
314
|
+
"""Called when a chain finishes."""
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
def on_chain_error(
|
|
318
|
+
self,
|
|
319
|
+
error: BaseException,
|
|
320
|
+
*,
|
|
321
|
+
run_id: Any = None,
|
|
322
|
+
**kwargs: Any,
|
|
323
|
+
) -> None:
|
|
324
|
+
"""Called when a chain errors."""
|
|
325
|
+
pass
|
|
326
|
+
|
|
327
|
+
# ── Stats ────────────────────────────────────────────────
|
|
328
|
+
|
|
329
|
+
def get_stats(self) -> dict[str, Any]:
|
|
330
|
+
"""Return governance statistics for this handler."""
|
|
331
|
+
return {
|
|
332
|
+
"session_id": self.session_id,
|
|
333
|
+
"call_count": self._call_count,
|
|
334
|
+
"block_count": self._block_count,
|
|
335
|
+
"redact_count": self._redact_count,
|
|
336
|
+
"features": {
|
|
337
|
+
"firewall": self.firewall,
|
|
338
|
+
"pii_redaction": self.pii_redaction,
|
|
339
|
+
"loop_detection": self.loop_detection,
|
|
340
|
+
},
|
|
341
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
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 plugin system.
|
|
16
|
+
|
|
17
|
+
Provides abstract base classes for all 9 plugin interfaces.
|
|
18
|
+
Community developers extend these to add new model adapters,
|
|
19
|
+
data connectors, governance guards, and more.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from admina.plugins.base import (
|
|
25
|
+
BaseAlertChannel,
|
|
26
|
+
BaseAuthProvider,
|
|
27
|
+
BaseComplianceTemplate,
|
|
28
|
+
BaseDataConnector,
|
|
29
|
+
BaseForensicStore,
|
|
30
|
+
BaseGovernanceGuard,
|
|
31
|
+
BaseModelAdapter,
|
|
32
|
+
BasePIIEngine,
|
|
33
|
+
BaseTransportAdapter,
|
|
34
|
+
)
|
|
35
|
+
from admina.plugins.registry import PLUGIN_TYPES, PluginRegistry
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"BaseModelAdapter",
|
|
39
|
+
"BaseDataConnector",
|
|
40
|
+
"BaseGovernanceGuard",
|
|
41
|
+
"BaseComplianceTemplate",
|
|
42
|
+
"BaseTransportAdapter",
|
|
43
|
+
"BaseForensicStore",
|
|
44
|
+
"BaseAuthProvider",
|
|
45
|
+
"BasePIIEngine",
|
|
46
|
+
"BaseAlertChannel",
|
|
47
|
+
"PluginRegistry",
|
|
48
|
+
"PLUGIN_TYPES",
|
|
49
|
+
]
|