feat: Plugin-System Umbau — 6 Phasen komplett abgeschlossen
Check Cross-Plugin Imports / check (push) Has been cancelled

Phase 1: Contracts konsequent nutzen
- 12 neue contracts.py erstellt (alle 19 Plugins haben jetzt contracts)
- 4 bestehende contracts.py an zentrale ContractRegistry angepasst
- Alle 19 Plugins haben on_deactivate mit Contract-Unregister
- 0 echte problematische INTER-Plugin Imports

Phase 2: Hooks/Filters-System
- app/core/hooks.py (HookRegistry mit actions + filters)
- 15 Hook-Punkte in Core-Services (contact, auth, mail, calendar, user, dms)
- BasePlugin.on_deactivate meldet alle Hooks ab

Phase 3: Plugin-Isolation
- scripts/check_cross_plugin_imports.py (Linting-Regel)
- .github/workflows/check-cross-plugin-imports.yml (CI/CD)
- .pre-commit-cross-plugin.yaml (Pre-commit Hook)
- 155 Dateien geprueft, 0 Verstoesse

Phase 4: Plugin-Versioning
- app/plugins/semver.py (SemVer mit Parse, Compare, Pre-release)
- migration_runner.py erweitert: run_migration_down, rollback_to_version
- manifest.py: min_app_version Feld
- registry.py: App-Version-Compatibility-Check bei Installation
- GET /api/v1/plugins/updates Endpoint

Phase 5: Marketplace-Vorbereitung
- app/plugins/signature.py (Ed25519 Signatur-Validierung)
- app/plugins/quarantine.py (Plugin-Quarantine mit Validierung)
- app/models/plugin_allowlist.py + Migration 0046
- manifest.py: author, license, homepage, icon, screenshots, changelog, marketplace_tags, price
- registry.py: discover_external(), discover_all()
- POST /api/v1/plugins/install-marketplace (deaktiviert)

Phase 6: Manifest-Anpassung
- manifest.py: 12 neue Felder + SemVer/Hook-Name Validierung
- MANIFEST_SCHEMA_DOC aktualisiert
- Alle 19 Plugin-Manifeste aktualisiert
- Frontend PluginUiManifest Typ erweitert

Zusaetzliche Bug-Fixes:
- test_sample-Modul erstellt
- conftest.py Deadlock-Prevention
- SESSION_COOKIE_SECURE=true
- dump.rdb aus Git entfernt + .gitignore
- backup.py datetime.utcnow -> func.now()
- system_settings.py JSONB-Import nach oben
- tax.py Mapped[float] -> Mapped[Decimal]
- notification.py type_key-Laengen vereinheitlicht

Tests: 91 neue Tests, alle bestanden
This commit is contained in:
Agent Zero
2026-07-26 23:15:34 +02:00
parent 744d595cae
commit 98eb1d0d89
62 changed files with 3284 additions and 18 deletions
+86
View File
@@ -232,6 +232,31 @@ class PluginManifest(BaseModel):
custom_fields: list[CustomFieldDefinition] = Field(
default_factory=list, description="Custom field definitions contributed by this plugin"
)
# ── Versioning (Phase 4) ──
min_app_version: str = Field(
default="0.0.0",
description="Minimum LeoCRM version required (SemVer)"
)
# ── Marketplace (Phase 5) ──
author: str = Field(default="", max_length=200, description="Plugin author name")
author_email: str = Field(default="", max_length=200, description="Author contact email")
homepage: str = Field(default="", max_length=500, description="Plugin homepage URL")
license: str = Field(default="MIT", max_length=50, description="License identifier")
icon: str = Field(default="", description="Icon URL or emoji")
screenshots: list[str] = Field(default_factory=list, description="Screenshot URLs for marketplace")
changelog: str = Field(default="", description="Changelog URL or inline text")
marketplace_tags: list[str] = Field(default_factory=list, description="Marketplace category tags")
price: float = Field(default=0.0, ge=0.0, description="Price (0 = free)")
# ── Hooks (Phase 2) ──
hooks: list[str] = Field(
default_factory=list,
description="Hook names this plugin registers (e.g. 'contact.before_create')"
)
# ── Contracts (Phase 1) ──
contract_version: str = Field(
default="1.0.0",
description="Contract API version this plugin exposes"
)
@field_validator("name")
@@ -241,6 +266,27 @@ class PluginManifest(BaseModel):
raise ValueError("Plugin name must be alphanumeric with underscores only")
return v.lower()
@field_validator("min_app_version")
@classmethod
def validate_min_app_version(cls, v: str) -> str:
"""Validate min_app_version is valid SemVer."""
if v and v != "0.0.0":
from app.plugins.semver import SemVer
SemVer.parse(v) # Raises ValueError if invalid
return v
@field_validator("hooks")
@classmethod
def validate_hooks(cls, v: list[str]) -> list[str]:
"""Validate hook names follow namespace.action pattern."""
import re
for hook in v:
if not re.match(r"^[a-z_]+\.[a-z_]+$", hook):
raise ValueError(
f"Invalid hook name '{hook}': must be 'namespace.action' (lowercase, underscores only)"
)
return v
model_config = {"extra": "forbid"}
@@ -323,6 +369,46 @@ MANIFEST_SCHEMA_DOC = ManifestSchemaResponse(
"required": "false",
"description": "Dashboard widgets (id, label_key, component, col_span, permission)",
},
"min_app_version": {
"type": "str",
"required": "false",
"description": "Minimum LeoCRM version required (SemVer, default: 0.0.0)",
},
"author": {
"type": "str",
"required": "false",
"description": "Plugin author name",
},
"license": {
"type": "str",
"required": "false",
"description": "License identifier (default: MIT)",
},
"homepage": {
"type": "str",
"required": "false",
"description": "Plugin homepage URL",
},
"hooks": {
"type": "list[str]",
"required": "false",
"description": "Hook names this plugin registers (e.g. 'contact.before_create')",
},
"contract_version": {
"type": "str",
"required": "false",
"description": "Contract API version this plugin exposes (default: 1.0.0)",
},
"marketplace_tags": {
"type": "list[str]",
"required": "false",
"description": "Marketplace category tags",
},
"price": {
"type": "float",
"required": "false",
"description": "Price (0 = free, default: 0.0)",
},
},
example=PluginManifest(
name="example_plugin",