docs(f3): plugin checklist + architecture requirements section in dev guide
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user