Decouple core from plugins; wire widgets hook; drop dead search hook

Architectural pass from the skill review ("plugin is the product" boundary).

Core no longer imports plugin models at module load (was a hard import-time
dependency that broke core if the computers plugin was absent/disabled):
- collector.py, applications.py, reports.py: lazy + guarded imports of the
  computers plugin models. Endpoints that need install-tracking now return 503
  when the plugin is absent instead of failing at import.

Search honors runtime enable/disable:
- search.py: _require_enabled(name) raises ImportError for a disabled plugin,
  so each plugin-scoped block skips it (a disabled plugin's rows leave search).
- Replace hardcoded root/rootpassword employee-DB connection in _search_employees
  with the shared env-backed employee_connection helper.

Plugin hooks (integrating-plugin-hooks: every hook needs a consumer):
- get_dashboard_widgets: add the consumer GET /api/dashboard/widgets (5 plugins
  already implemented the hook; it had none). Skips disabled, isolates in prod.
- get_searchable_fields: REMOVED. Zero plugins implemented it and there was no
  consumer; global search is a core concern over the asset model. Contract
  reduction, __contract_version__ 0.3.0 -> 0.4.0.

Docs/contract: PLUGIN-HOOKS.md (widgets consumer note, searchable-fields removal,
0.4.0), PLUGIN-QUICKSTART.md, ADR-001 hook list. Tests: widgets endpoint
aggregate + disabled-skip; contract tests for the removed/added hooks.

151 tests pass, naming/style green, app boots all 6 plugins.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-06-26 20:00:10 -04:00
parent 6979edfc0c
commit a515c28e3b
12 changed files with 940 additions and 792 deletions

View File

@@ -9,7 +9,7 @@ The contract is locked in [ADR-001](../docs/adr/ADR-001-asset-as-platform-contra
The framework declares its contract version in `shopdb/__init__.py`:
```python
__contract_version__ = '0.3.0'
__contract_version__ = '0.4.0'
```
Each plugin's `manifest.json` declares the range of contract versions it supports:
@@ -166,6 +166,10 @@ class NotificationsPlugin(BasePlugin):
}]
```
Consumed by `GET /api/dashboard/widgets`, which merges widgets from all enabled
plugins sorted by `position` (disabled plugins are skipped; a broken plugin is
isolated in prod, re-raised in dev/test).
### `get_navigation_items() -> List[Dict]`
Returns navigation menu items.
@@ -181,24 +185,10 @@ class ComputersPlugin(BasePlugin):
}]
```
### `get_searchable_fields() -> List[Dict]`
Declares fields the plugin contributes to global search.
```python
from .models import Computer
class ComputersPlugin(BasePlugin):
def get_searchable_fields(self):
return [{
'model': Computer,
'search_fields': ['hostname', 'serialnumber', 'currentuser'],
'result_type': 'computer',
'url_template': '/computers/{id}',
'title_field': 'hostname',
'subtitle_field': 'currentuser',
}]
```
> Removed in contract 0.4.0: `get_searchable_fields`. Global search
> (`/api/search`) is a core concern that queries the asset model directly and
> already covers every bundled asset type; no plugin ever implemented the hook.
> Search honors runtime plugin enable/disable.
### `get_collector_schema() -> Optional[Dict]`

View File

@@ -119,10 +119,9 @@ Override hooks on the plugin class as needed. See [PLUGIN-HOOKS.md](PLUGIN-HOOKS
| Hook | Adds |
|------|------|
| `get_searchable_fields` | Plugin contributes to the global search endpoint |
| `get_navigation_items` | Plugin shows up in the sidebar nav |
| `get_dashboard_widgets` | Plugin's dashboard widget appears on the home page |
| `get_collector_schema` | Plugin accepts external pushes at `/api/collector/<name>` |
| `get_collector_schema` + `apply_collector_payload` | Plugin accepts external pushes at `/api/collector/<name>` |
Each hook has a default that does nothing. Override only what your plugin needs.

View File

@@ -58,7 +58,7 @@ Adding a name here is a minor (additive) contract change; removing one is major.
#### Plugin contract
- `BasePlugin` ABC and its hooks (search, navigation, dashboard, relationships, collector schema + `apply_collector_payload`)
- `BasePlugin` ABC and its hooks (navigation, dashboard widgets, collector schema + `apply_collector_payload`). Note: `get_searchable_fields` was removed in contract 0.4.0 - global search is a core concern over the asset model, not a per-plugin hook.
#### Excluded from the contract for v1