3.5 KiB
Plugin Checklist — MUST-PASS before merge
Every new or changed plugin must pass this checklist. Each item maps to a failure class that actually occurred in this codebase (see PROGRESS.md architecture-repair section). CI runs the automated checks; reviewers verify the rest.
Automated checks (CI)
python scripts/check_cross_plugin_imports.py→ 0 violations (no direct imports from other plugins — useget_contract())python scripts/check_migration_hashes.py→ OK (migrations ≤0092 unchanged)npx tsc --noEmitclean (if frontend contributions changed)- pytest smoke of affected suites passes
Architecture rules
-
No cross-plugin imports — access other plugins only via
from app.plugins.builtins.contracts import get_contract. If a contract is missing, define it in the owning plugin'scontracts.py. Failure class: silent dead code when the imported symbol moved. -
Lifecycle symmetry — everything registered in
on_activate(contracts, services, event handlers, hooks, tools, providers) must be deregistered inon_deactivate, in the correct order: own cleanup FIRST, thensuper().on_deactivate(). Failure class: stale services after deactivation; ImportError at every deactivate because a helper was imported as module function. -
Every route has a permission — no auth-only routes. Every
FrontendMenuItemandFrontendPageRoutecarries itspermissionfield. Failure class: dead guards checking permissions that don't exist. -
Declare dependencies — if your plugin uses another plugin's data or contracts, declare it in
manifest.dependencies. Activation order is topological; your plugin cannot be deactivated while dependents are active. -
Entities via registration — return models from
get_entity_models(); never assume core tables. The/entity-permissions/registryendpoint is generated dynamically from these registrations. -
Frontend components via manifest — pages, menu items, settings pages, detail tabs, dashboard widgets come from the manifest. Register new page components in
frontend/src/components/plugins/PluginLoader.tsxSTATIC_COMPONENT_MAP so production builds can chunk them. Failure class: ghost components — manifest references a component that does not exist; tab shows error boundary in production. -
Migrations follow convention — Alembic revisions touching plugin-owned tables must be conditional (
to_regclassguard) with an idempotent plugin-side SQL migration for dual-path convergence. Failure class: fresh-install breaks because Alembic ran before plugins. -
Audit log on every mutation — use
log_auditfromapp.core.audit. -
datetime only with UTC —
datetime.now(UTC), neverutcnow(). -
Pydantic schemas validate input — no raw dict bodies on routes.
-
Agent tools carry permissions — any tool registered in the tool registry declares the permission of the underlying endpoint.
-
Tests against real PostgreSQL — ephemeral DB per run (see
app/plugins/builtins/automation/tests/test_automation.pyfixture); create real tenant/user rows instead of random UUIDs for FK columns.
Review checklist (human/agent reviewer)
- Checklist items above verified, not assumed
- New mechanisms documented in
docs/plugin-development-guide.md - PROGRESS.md updated with finding ID + commit hash + verification proof