79 lines
4.1 KiB
Markdown
79 lines
4.1 KiB
Markdown
# [DEF:ADR-0004:ADR]
|
||
# @STATUS ACTIVE
|
||
# @PURPOSE Define the plugin architecture for ss-tools — the loading mechanism, lifecycle contract, isolation guarantees, and the boundary between core services and pluggable extensions.
|
||
# @RELATION DEPENDS_ON -> [ADR-0001:ADR]
|
||
# @RELATION DEPENDS_ON -> [ADR-0003:ADR]
|
||
# @RELATION CALLS -> [ADR-0005:ADR]
|
||
# @RATIONALE Extensibility is a core architectural value: the system must support LLM‑driven analysis, custom data transformations, and environment‑specific logic without modifying core code. A plugin system prevents the monolith from accumulating every domain‑specific feature and enables third‑party (or future‑self) contributions without forking.
|
||
# @RATIONALE Process isolation was chosen over in‑process imports because: (a) plugins may use incompatible library versions, (b) a crashing plugin must not take down the orchestrator, (c) security boundary — plugins should not access the orchestrator's database connection directly.
|
||
# @REJECTED In‑process Python `importlib` plugin loading — rejected because a misbehaving plugin can corrupt global state, exhaust memory, or crash the server. Process isolation provides a hard boundary.
|
||
# @REJECTED Docker‑container per plugin — rejected because it adds excessive orchestration complexity and startup latency for plugins that are mostly lightweight LLM prompt chains. Subprocess isolation is sufficient.
|
||
# @REJECTED WebAssembly (WASI) sandbox — rejected because the Python AI/LLM ecosystem (langchain, transformers) does not yet reliably compile to WASM. Premature optimization.
|
||
|
||
## Decision
|
||
|
||
### Plugin Architecture
|
||
|
||
```
|
||
ss-tools Core
|
||
├── core/plugin_loader.py # Plugin discovery, loading, lifecycle
|
||
├── core/plugin_executor.py # Subprocess execution, timeout, error boundary
|
||
├── core/plugin_registry.py # Registered plugins, metadata, health
|
||
└── plugins/ # Plugin packages (each = one directory)
|
||
├── llm_analysis/ # LLM‑driven Superset data analysis
|
||
├── dataset_orchestration/ # LLM dataset operations
|
||
└── git_integration/ # Git‑based version control for dashboards
|
||
```
|
||
|
||
### Plugin Contract
|
||
|
||
Every plugin MUST provide:
|
||
|
||
1. **`plugin.toml`** — metadata manifest at the plugin root
|
||
```toml
|
||
[plugin]
|
||
id = "llm_analysis"
|
||
name = "LLM Data Analysis"
|
||
version = "1.0.0"
|
||
entrypoint = "plugin.py"
|
||
timeout_sec = 300
|
||
max_memory_mb = 512
|
||
requires = ["superset-api>=1.0", "openai>=1.0"]
|
||
```
|
||
|
||
2. **`plugin.py`** — entrypoint with two required functions:
|
||
- `def register(registry: PluginRegistry) -> PluginInfo` — declare capabilities
|
||
- `def execute(task: TaskContext) -> TaskResult` — run the plugin
|
||
|
||
3. **Task context contract** (Pydantic `TaskContext`):
|
||
- `task_id: str`, `plugin_id: str`, `action: str`, `params: dict`, `superset_env: SupersetConnection`, `auth_token: str` (scoped, short‑lived)
|
||
|
||
4. **Result envelope** (Pydantic `TaskResult`):
|
||
- `status: Literal["success", "warning", "error"]`, `data: dict | None`, `error_message: str | None`, `execution_time_ms: int`, `artifacts: list[str]` (file paths to saved artifacts)
|
||
|
||
### Plugin Lifecycle
|
||
|
||
```
|
||
Discover → Validate → Register → [Execute] → Report
|
||
│ │ │ │
|
||
│ Check TOML Store in Spawn subprocess
|
||
│ schema, registry with timeout +
|
||
│ dependencies in DB memory limit
|
||
```
|
||
|
||
### Isolation Guarantees
|
||
|
||
- **Subprocess**: `subprocess.run(..., timeout=timeout_sec)`, killed on timeout.
|
||
- **Memory**: `resource.setrlimit(RLIMIT_AS, max_memory_mb * 1024 * 1024)` before exec.
|
||
- **No DB access**: Plugins receive only a scoped REST API token, never a database connection.
|
||
- **No filesystem writes outside allowed dirs**: Configurable artifact directory per plugin.
|
||
|
||
### RBAC Integration
|
||
|
||
Plugin access is governed by ADR-0005 (RBAC):
|
||
- Each plugin declares `required_roles: ["admin", "analyst"]` in `plugin.toml`.
|
||
- Plugin executor checks the user's role set before allowing execution.
|
||
- Forbidden access returns `403` with audit log entry.
|
||
|
||
# [/DEF:ADR-0004:ADR]
|