Files
crm-system/.a0/briefings/T03_briefing.md
T
leocrm-bot 6bf0746b94 T02: companies + contacts + import/export + N:M + soft-delete + GDPR + FTS
- Company CRUD with soft-delete, FTS search (tsvector + GIN), filter, pagination
- Contact CRUD with N:M company linking via company_contacts
- CSV/XLSX export, CSV import with dry-run preview
- GDPR hard-delete with deletion_log
- Audit log on all mutations
- 27 new tests (24 ACs), 56 total tests pass
- Migration 0002: contacts, company_contacts, FTS search_tsv
- Fixed T01 tests: POST→201, PATCH→PUT compatibility
2026-06-29 00:44:34 +02:00

6.3 KiB

T03: Plugin System Framework

Context

  • Project: LeoCRM (greenfield rewrite, Option C)
  • Repo: /a0/usr/workdir/dev-projects/leocrm
  • T01 COMPLETE: auth, multi-tenant, RBAC, sessions, audit, notifications
  • T01 commit: 7a7daf8 (pushed to Forgejo)
  • Tech: FastAPI + SQLAlchemy 2.0 async + PostgreSQL + Redis + Pydantic v2

Existing T01 Code to Build On

  • app/core/event_bus.py — Event bus (0% coverage, needs integration)
  • app/core/service_container.py — DI container (0% coverage, needs integration)
  • app/core/db/__init__.py — Engine, Session, Base, TenantMixin, set_tenant_context
  • app/deps.py — get_current_user, require_admin, get_tenant_id
  • app/core/audit.py — log_audit function
  • app/main.py — create_app factory, mounts routers, middleware
  • app/routes/__init__.py — router aggregator
  • tests/conftest.py — Test fixtures with TRUNCATE CASCADE

Requirements (7)

F-PLUGIN-01: Plugin-System für Module — Module als Plugins, Daten austauschbar F-PLUGIN-02: Plugin-Schnittstellen-Definition — API-Contract, Lifecycle-Hooks, Manifest, Abhängigkeiten F-CORE-01: Multi-Tenant-Architektur F-CORE-03: RBAC F-CORE-04: Audit-Log F-CORE-05: Event-System F-TEST-01: Test-Coverage

Acceptance Criteria (14)

  1. GET /api/v1/plugins → 200 + list of plugins with status
  2. POST /api/v1/plugins/{name}/install → 200, plugin status=installed, migrations run
  3. POST /api/v1/plugins/{name}/activate → 200, plugin status=active, routes registered
  4. POST /api/v1/plugins/{name}/deactivate → 200, plugin status=inactive, routes unregistered
  5. DELETE /api/v1/plugins/{name} → 200, plugin removed
  6. DELETE /api/v1/plugins/{name}?remove_data=true → 200, plugin tables dropped
  7. GET /api/v1/plugins/manifest → 200 + manifest schema documentation
  8. Plugin activation registers event listeners on event bus
  9. Plugin deactivation unregisters event listeners
  10. Plugin migration creates tables with tenant_id column
  11. Plugin migration validator rejects tables without tenant_id
  12. Plugin DB migrations tracked in plugin_migrations table
  13. Activating already-active plugin → idempotent (200, no error)
  14. Deactivating inactive plugin → idempotent (200)

Files to Create/Modify

New Files:

  • app/models/plugin.py — Plugin, PluginMigration (plugin_migrations tracking table)
  • app/schemas/plugin.py — Plugin schemas (manifest, status, install/activate response)
  • app/services/plugin_service.py — Plugin lifecycle: discover, install, activate, deactivate, uninstall
  • app/routes/plugins.py — Plugin endpoints (list/install/activate/deactivate/uninstall/manifest)
  • app/plugins/__init__.py — Plugin package init
  • app/plugins/base.py — BasePlugin abstract class with lifecycle hooks
  • app/plugins/manifest.py — PluginManifest Pydantic schema (name, version, dependencies, routes, events, migrations)
  • app/plugins/registry.py — Plugin registry (in-memory + DB-backed status)
  • app/plugins/migration_runner.py — Plugin DB migration runner + validator (tenant_id check)
  • app/plugins/builtins/__init__.py — Built-in plugins directory (empty for now, just structure)
  • tests/test_plugins.py — Plugin lifecycle tests (install/activate/deactivate/uninstall/idempotent)

Modify:

  • app/models/__init__.py — Register Plugin, PluginMigration
  • app/routes/__init__.py — Register plugins router
  • app/schemas/__init__.py — Register plugin schemas
  • app/services/__init__.py — Register plugin_service
  • app/main.py — Initialize plugin registry on startup (discover builtins)
  • app/core/event_bus.py — Ensure register/unregister listener API works for plugins
  • app/core/service_container.py — Ensure plugins can receive db, cache, event_bus, storage, notifications
  • tests/conftest.py — Add plugins, plugin_migrations to TRUNCATE list
  • alembic/versions/ — New migration for plugins + plugin_migrations tables

Plugin Lifecycle Design

discovered → installed → active → inactive → uninstalled
                ↑                      ↓
                └──────────────────────┘ (can re-activate)

Plugin Manifest Schema (Pydantic v2)

class PluginManifest(BaseModel):
    name: str  # unique identifier
    version: str  # semver
    display_name: str
    description: str
    dependencies: list[str] = []  # other plugin names required
    routes: list[dict] = []  # route definitions
    events: list[str] = []  # event names to listen
    migrations: list[str] = []  # migration file names
    permissions: list[str] = []  # required permissions

BasePlugin Abstract Class

class BasePlugin(ABC):
    manifest: PluginManifest
    
    async def on_install(self, db, service_container): ...
    async def on_activate(self, db, service_container, event_bus): ...
    async def on_deactivate(self, db, service_container, event_bus): ...
    async def on_uninstall(self, db, service_container): ...
    def get_routes(self) -> list[APIRouter]: ...

Forbidden Patterns

  • NO JWT tokens (session-based auth from T01)
  • NO SQLite (PostgreSQL only)
  • NO wildcard CORS
  • NO raw SQL without tenant context
  • NO hardcoded secrets
  • NO .test TLD emails (use .com)
  • NO SET LOCAL with bound params (use SELECT set_config())
  • NO raising HTTPException in middleware (return JSONResponse)
  • NO POST without status_code=201 (where applicable)
  • NO plugin tables without tenant_id column (validator enforces)

Test Spec

  • Commands: cd /a0/usr/workdir/dev-projects/leocrm && source venv/bin/activate && python -m pytest tests/test_plugins.py -v --tb=short
  • Coverage: python -m pytest tests/test_plugins.py --cov=app/plugins --cov-report=term-missing
  • Target: 85% for plugin modules
  • All 14 ACs must pass

Token Rule

  • Use text_editor:read for MODIFY, text_editor:write for NEW, code_execution_tool:terminal for test runs
  • Reference files by path, not inline
  • Multi-file output: separate files, not one big file

JSON Tool Examples

{"tool_name":"text_editor","tool_args":{"action":"write","path":"/a0/usr/workdir/dev-projects/leocrm/app/plugins/base.py","content":"..."}}
{"tool_name":"code_execution_tool","tool_args":{"runtime":"terminal","session":0,"code":"cd /a0/usr/workdir/dev-projects/leocrm && source venv/bin/activate && python -m pytest tests/test_plugins.py -v --tb=short"}}