Files
ss-tools/docs/adr/ADR-0004-plugin-architecture.md
busya 731aaaa8df feat(mcp): 050 unified MCP interface — parity tools, durable gates, authoring E2E, assistant decommission
044 provider runtime completion (pre-staged workstream): capacity/operator
stores, provider ops/protocol/reconciler/dispatch revalidation, exploration
sandbox runtime, alembic 0014-0016, live canary evidence (browser provider
6/6 against the live stand).

050 Phases 0-2: RBAC FastMCP server with a 45-tool explicit catalog,
OAuth/DCR transport guards with bounded bodies, per-call provenance
(McpToolInvocationRecord), durable ActionApprovalGate + CAS decide +
leased/fenced poller, authoring workspace ops, bounded-response discipline,
hidden-vs-gated matrices.

Parity domains (T012-T014): gated git/deploy/migration/backup/llm tools with
reviewed dispatch adapters in explicit poll chains; Superset reads/writes with
a dedicated plugin:superset_sql risk class (terminal PROD denial via the
canonical execution-policy criterion, hardened danger-SQL guard covering
INTO/CALL/SET/REFRESH/file primitives/multi-statement); baseline 037 tools
over the shared REST-surface services.

Evidence (T016/T023/T028): REST-vs-MCP field parity on shared 037 fixtures;
vertical E2E from tools/list through registry revision activation with real
scenario:EDIT RBAC; sandbox-to-revision promotion E2E with unsafe-payload and
caller-digest rejection; dispatcher soak (three poll cycles, exactly-once).

Orthogonal QA+security audit hardening: enforced response_limit fail-closed
envelope, poisoned-exploration fail-closed (EXPLORATION_TARGET_UNRESOLVED),
sha256 exploration evidence digests, actor-UUID task ownership, is_active
guard on baseline consume, 038 resolver description=None selector fix.

Phase 3/4 decommission: HandoffSurface behind the MCP_DECOMMISSION flag,
then unconditional removal — agent/ service tree, chat components/models/
stores/types, gradio proxies (vite + nginx), agent service in run.sh,
docker-compose profiles, build.sh bundles; /agent renders the handoff only.
Docs: AGENTS.md/INSTALL.md two-service rewrite; 036-047 drift amendments
marked done; WORKSTATE checkpoints with all evidence.

Suites: backend 11199 passed / 240 skipped / 1 xpassed; frontend 3454 passed
(197 files), lint 0 errors, build OK; browser E2E login+handoff 6/6 twice on
the isolated compose stack (no 7860); ruff/compileall clean.

Misc: gitignore hardening (tmp/, tool model cache); E2E selector repairs
(nav strict-mode, invalid-credentials passthrough detail).
2026-09-03 07:37:14 +03:00

4.6 KiB
Raw Blame History

@{ Doc.Adr.ADR0004 [C:1] [TYPE ADR]

@STATUS SUPERSEDED

@BRIEF Historical proposal for subprocess-isolated, TOML-manifest plugins. Superseded by ADR-0016, which records the implemented in-process PluginBase runtime.

@REPLACED_BY [ADR-0016:ADR]

@RELATION DEPENDS_ON -> [Doc.Adr.ADR0001]

@RELATION DEPENDS_ON -> [Doc.Adr.ADR0003]

@RELATION CALLS -> [Doc.Adr.ADR0005]

@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

superset-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

    [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.

@} Doc.Adr.ADR0004