Files
ss-tools/docs/adr/ADR-0004-plugin-architecture.md
2026-05-08 18:01:49 +03:00

79 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# [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 LLMdriven analysis, custom data transformations, and environmentspecific logic without modifying core code. A plugin system prevents the monolith from accumulating every domainspecific feature and enables thirdparty (or futureself) contributions without forking.
# @RATIONALE Process isolation was chosen over inprocess 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 Inprocess 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 Dockercontainer 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/ # LLMdriven Superset data analysis
├── dataset_orchestration/ # LLM dataset operations
└── git_integration/ # Gitbased 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, shortlived)
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]