docs(f3): plugin checklist + architecture requirements section in dev guide
This commit is contained in:
@@ -366,6 +366,86 @@ class MyPlugin(BasePlugin):
|
||||
|
||||
---
|
||||
|
||||
## 3.1 Architecture Requirements (v3) — MUST READ
|
||||
|
||||
> **Every plugin must pass `docs/plugin-checklist.md` before merge.** The
|
||||
> checklist maps each rule to a failure class that actually occurred in this
|
||||
> codebase. Summary of the mechanisms introduced by the architecture repair
|
||||
> (Blocks A–C):
|
||||
|
||||
### Contracts — the ONLY way to reach other plugins
|
||||
|
||||
```python
|
||||
from app.plugins.builtins.contracts import get_contract
|
||||
|
||||
contract = get_contract("kommunikation")
|
||||
if contract is None or not hasattr(contract, "send_message"):
|
||||
logger.warning("kommunikation contract unavailable - skipping")
|
||||
return # or degrade gracefully — NEVER import from the other plugin directly
|
||||
result = await contract.send_message(...)
|
||||
```
|
||||
|
||||
Rules:
|
||||
- If a contract is missing, define it in the **owning** plugin's `contracts.py`
|
||||
(self-registers via `get_contract_registry().register()`).
|
||||
- Contracts must be **unregistered on deactivate** and re-registered on
|
||||
activate (symmetry).
|
||||
- CI gate: `python scripts/check_cross_plugin_imports.py` → 0 violations.
|
||||
|
||||
### Lifecycle symmetry & ordering
|
||||
|
||||
Everything registered in `on_activate` (contracts, services, event handlers,
|
||||
hooks, tools, search providers) MUST be deregistered in `on_deactivate`. Own
|
||||
cleanup runs FIRST, then `super().on_deactivate()`. On activate, call
|
||||
`super().on_activate()` FIRST, then own registrations.
|
||||
|
||||
Hook helpers live on the registry singleton, NOT as module functions:
|
||||
|
||||
```python
|
||||
from app.core.hooks import get_hook_registry
|
||||
get_hook_registry().register_action("wiki.article.created", handler, owner_tag="wiki")
|
||||
# deactivate:
|
||||
get_hook_registry().unregister_all_for_plugin("wiki")
|
||||
```
|
||||
|
||||
### Dependencies (`dependencies=[...]`)
|
||||
|
||||
Declare every plugin you depend on in the manifest. Activation order is
|
||||
topological (Kahn), activation fails with a clear error if a dependency is
|
||||
inactive, and deactivation of a dependency is **blocked** while your plugin
|
||||
is active.
|
||||
|
||||
### Permission fields are MANDATORY on UI contributions
|
||||
|
||||
`FrontendMenuItem.permission`, `FrontendPageRoute.permission`,
|
||||
`FrontendSettingsPage.permission`, `FrontendDashboardWidget.permission` —
|
||||
empty string means "any authenticated user". The route renderer enforces them
|
||||
via `ProtectedRoute`; the sidebar/settings filter by them.
|
||||
|
||||
### Frontend components: register for production builds
|
||||
|
||||
New page components must be added to `STATIC_COMPONENT_MAP` in
|
||||
`frontend/src/components/plugins/PluginLoader.tsx`. Vite cannot chunk runtime-built
|
||||
import paths — unregistered components work in dev but fail in production.
|
||||
Also verify the component file actually exists (ghost references show an
|
||||
error boundary in production).
|
||||
|
||||
### Migrations touching plugin-owned tables
|
||||
|
||||
Alembic revisions must guard with `to_regclass` and skip when the table does
|
||||
not exist yet; the plugin-side SQL migration adds the same schema idempotently
|
||||
(dual-path convergence). See migrations 0119–0141 + plugin migration files
|
||||
for the pattern.
|
||||
|
||||
### Tests run against real PostgreSQL
|
||||
|
||||
Use the ephemeral-database fixture pattern from
|
||||
`app/plugins/builtins/automation/tests/test_automation.py`: create/drop a DB
|
||||
per run, enable pgvector, import all models before `create_all`, and create
|
||||
real tenant/user rows instead of random UUIDs for FK columns.
|
||||
|
||||
---
|
||||
|
||||
## 4. Plugin Lifecycle
|
||||
|
||||
### 4.1 Installation (`on_install`)
|
||||
|
||||
Reference in New Issue
Block a user