fix(arch): externes Audit — 13 Backend-Fixes (Workspace-Modules, Tenant-Manifeste, Lifecycle, Contracts, Permissions)
Check Cross-Plugin Imports / check (push) Has been cancelled

Verifikation: Alle 17 Audit-Findings gegen den Code geprueft — alle bestaetigt.
Backend-Lifecycle-Fixes umgesetzt; 4 Frontend-Plugin-Architektur-Punkte
als Phase Q in die Roadmap eingeplant.

- P1 list_workspaces: Module + User-Counts gebuendelt laden (Editor-Overwrite-Bug)
- P1 active-manifests: Tenant-Deaktivierung (tenant_plugin_activation) filtern
- P1 uninstall: volle Service-Deactivation VOR registry.uninstall()
- P1 ContractRegistry: DB-Aktivstatus-Guard (Restart-Edge-Case) + Re-Activate
- P1/P2 Field-Definitions: voller Lifecycle (register/unregister) im Service
- P1/P2 Contact-Felddefinitionen (39) ins ContactsPlugin-Manifest verschoben
- P1 12 fehlende Permission-Keys registriert (AST-Scan: 0 fehlend)
- P2 contact_folder -> ContactsPlugin; ENTITY_PLUGIN_OWNERS wird befuellt
- P2 Entity-Permission-Fallback fail-closed statt contacts:read
- P2 forgejo_error_reporter is_core=False; DMS is_core=True (ADR-020)
- P2 Worker: Contacts-Trash-Cleanup ins Plugin (get_job_modules-Discovery)
- P1/P2 DSGVO-Export delegiert an DSAR-Collector (kein Core->Contacts)
- P2 False-green Tests korrigiert (or True, veraltete Route-Count-Assertion)

Verifikation: tests/test_audit_architecture_fixes.py 17/17; Regressionen
gruen (contacts_lifecycle, entity_registry, workspace_scopes, rbac,
lifecycle_service); Combo-Order-Test 35/35; Cross-Plugin-Checker 497/0;
compileall sauber; ruff auf 7-Error-Baseline.

Doku: PROGRESS.md Audit-Section, PLATFORM_ROADMAP.md Phase Q (Q1-Q4),
plugin-development-guide.md Lifecycle, permissions.md Katalog.
This commit is contained in:
Agent Zero
2026-09-13 02:25:01 +02:00
parent 86cea5d6c4
commit 4a25ac1379
25 changed files with 1020 additions and 141 deletions
+10
View File
@@ -60,6 +60,16 @@ Historical note (ARCH-008/009): migration 0019 seeded default roles with dead
3-segment patterns (`core:*:read` etc.); migration 0141 converts existing role
data to the canonical form.
**Catalog completeness (audit fix 2026-09-13):** 12 previously used-but-unregistered keys are now in the catalog so roles can actually be granted them:
- `CORE_PERMISSIONS` additions: `automation:admin`, `bank-accounts:read`, `bank-accounts:write`, `delegations:read`, `delegations:write`, `policies:read`, `policies:write`, `templates:read`, `templates:write`
- `permissions` plugin manifest: `permissions:read`, `permissions:admin`
- `forgejo_error_reporter` plugin manifest: `system:read`
Verified via AST scan (used keys vs. catalog): 146 registered, 0 missing.
**Entity-permission mapping fails closed (audit fix):** `get_entity_read_permission()` no longer falls back to `contacts:read` for unmapped entities — it returns the un-grantable sentinel `__unmapped__:read` (generic services then deny). Unknown entity types are rejected earlier with 422 by `validate_entity_type()`. Plugin-owned entities resolve correctly via `ENTITY_PLUGIN_OWNERS` (now populated through `register_entity_model(..., plugin_name=...)`).
---
## 2. Data Model
+11
View File
@@ -491,6 +491,17 @@ async def on_deactivate(
self._event_handlers.clear()
```
**Runtime lifecycle (service-managed, audit fix 2026-09-13):** `PluginService` registers/unregisters these contributions automatically on activate/deactivate — plugins do NOT need to do this manually:
- **Permissions** (`manifest.permissions`) — registered/unregistered in the permission registry
- **Entity models** (`get_entity_models()`) — registered in `ENTITY_MODELS` **with `plugin_name`**, so `get_entity_read_permission()` derives the owning module's read permission (e.g. `contacts:read` for `contact_folder`)
- **Field definitions** (`get_field_definitions()` / `manifest.field_definitions`) — registered/unregistered in the permission registry's field-definition store (full lifecycle since the audit fix)
- **Contracts** — the `ContractRegistry` fails closed for deactivated plugins (central unregister on deactivate; `mark_db_inactive()` at startup covers plugins already inactive in the DB — restart edge case)
**ADR-020 (is_core classification):** `is_core=True` means the plugin CANNOT be deactivated (`registry.deactivate()` rejects it). DMS is declared `is_core=True` because the core schema (`entity_attachments.files` FK) builds on its `files` table. Test-only plugins (e.g. `forgejo_error_reporter`) must be `is_core=False` so they stay deactivatable.
**Uninstall ordering (audit P1 fix):** `uninstall_plugin()` runs the FULL service deactivation first (`deactivate_plugin()`), then `registry.uninstall()` — so no stale permission/entity/contract registrations remain.
### 4.4 Uninstallation (`on_uninstall`)
Called when the plugin is uninstalled (before data tables are dropped). Override to clean up external resources.