# 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 — use `get_contract()`) - [ ] `python scripts/check_migration_hashes.py` → OK (migrations ≤0092 unchanged) - [ ] `npx tsc --noEmit` clean (if frontend contributions changed) - [ ] pytest smoke of affected suites passes ## Architecture rules 1. **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's `contracts.py`. *Failure class: silent dead code when the imported symbol moved.* 2. **Lifecycle symmetry** — everything registered in `on_activate` (contracts, services, event handlers, hooks, tools, providers) must be deregistered in `on_deactivate`, in the correct order: own cleanup FIRST, then `super().on_deactivate()`. *Failure class: stale services after deactivation; ImportError at every deactivate because a helper was imported as module function.* 3. **Every route has a permission** — no auth-only routes. Every `FrontendMenuItem` and `FrontendPageRoute` carries its `permission` field. *Failure class: dead guards checking permissions that don't exist.* 4. **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. 5. **Entities via registration** — return models from `get_entity_models()`; never assume core tables. The `/entity-permissions/registry` endpoint is generated dynamically from these registrations. 6. **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.tsx` STATIC_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.* 7. **Migrations follow convention** — Alembic revisions touching plugin-owned tables must be conditional (`to_regclass` guard) with an idempotent plugin-side SQL migration for dual-path convergence. *Failure class: fresh-install breaks because Alembic ran before plugins.* 8. **Audit log on every mutation** — use `log_audit` from `app.core.audit`. 9. **datetime only with UTC** — `datetime.now(UTC)`, never `utcnow()`. 10. **Pydantic schemas validate input** — no raw dict bodies on routes. 11. **Agent tools carry permissions** — any tool registered in the tool registry declares the permission of the underlying endpoint. 12. **Tests against real PostgreSQL** — ephemeral DB per run (see `app/plugins/builtins/automation/tests/test_automation.py` fixture); 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