# [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; v2 adds dual-path execution (Path A: Playwright screenshot + multimodal LLM; Path B: text-only API + dataset health checking)
    ├── dataset_orchestration/  # LLM dataset operations
    └── git_integration/        # Git‑based version control for dashboards
```

### Plugin Contract

> **v2 update:** Task-based validation was introduced via `ValidationTaskService`, creating persistent validation policies with `ValidationRun` aggregates for grouping per-dashboard results. Each run is tracked as a `ValidationRun` record with aggregate pass/fail/warn counts, replacing the previous single-shot task result pattern. See `ValidationTaskService` in `services/validation_service.py`.

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]
