Build the plugin set a real site runs, and write down what shipped
The lean-build job proved the delete-a-plugin guarantee with two plugins and four hand-picked marker components. It now builds a real twelve-plugin set, and the assertions follow a rule instead of a hunch: one component from EVERY omitted plugin must be absent, one from EVERY chosen plugin must be present, and the backend tree is checked both ways. An omitted set that grows without its check growing is how a leak ships. CHANGELOG had not been touched across the whole range, so the largest security-behaviour change in the product - per-PC endpoint credentials, the shared-key refusal for enrolled PCs, server pinning, agent 1.20.1 and tray 1.7.1 - had no release record. An [Unreleased] section now carries it, written from the commits rather than from memory. CLAUDE.md and the agent quick-reference stated that ADR-016 bans storing credentials in the registry. It does not, and this fleet depends on HKLM for its collector key and server pins: the next person following that text removes them and silently breaks reporting and pinning on every un-enrolled PC. Corrected, along with two other statements that were false on this branch - the database is MySQL, not PostgreSQL, and column names are lowercase concatenated, not snake_case, which is the opposite of what the gate enforces. The wiki drafts are updated against the code as it stands, each old finding given a verdict with a file and line rather than left as an assertion from a week ago, and a new page records the ADR conformance result - including that the ADR set itself is not on this branch, so nothing here enforces it except the naming and style script. They are also scrubbed of the things that must not reach a published wiki: the site name, the internal git server, developer home paths and the assistant tooling. They now pass the repository's own publishability patterns, which is what the scrub is for.
This commit is contained in:
@@ -2,7 +2,12 @@
|
||||
|
||||
**Status:** Accepted (2026-09-11)
|
||||
**Context:** Plugin UI extensions, lean builds
|
||||
**Supersedes:** Hardcoded plugin routes in core router
|
||||
**Supersedes:** Hardcoded plugin routes in core router
|
||||
**Last checked against code:** 2026-09-18, branch gh-wj at 35d4fcd41
|
||||
|
||||
This draft lives only in `docs/wiki-drafts/`. There is no `docs/adr/` directory
|
||||
on this branch, so this is the sole ADR-shaped record here and nothing enforces
|
||||
it except the tests named below. See ADR-Conformance-2026-09-18.md.
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -63,27 +68,44 @@ class ComputersPlugin(BasePlugin):
|
||||
|
||||
### Backend: API Endpoint
|
||||
|
||||
Added to `shopdb/core/api/plugins.py`:
|
||||
In `shopdb/core/api/plugins.py:46-95`, as it stands on gh-wj at 35d4fcd41:
|
||||
|
||||
```python
|
||||
@plugins_bp.route('/settings-routes', methods=['GET'])
|
||||
@jwt_required(optional=True)
|
||||
@require_permission('settings.view')
|
||||
def get_plugin_settings_routes():
|
||||
"""Return settings routes contributed by all enabled plugins."""
|
||||
pm = current_app.extensions.get('plugin_manager')
|
||||
if not pm:
|
||||
return success_response({'routes': []})
|
||||
|
||||
|
||||
routes = []
|
||||
for plugin in pm.get_enabled_plugin_instances():
|
||||
if hasattr(plugin, 'get_settings_routes'):
|
||||
for plugin_name, plugin in pm.get_all_plugins().items():
|
||||
# Only include routes from enabled plugins
|
||||
if not pm.registry.is_enabled(plugin_name):
|
||||
continue
|
||||
if not hasattr(plugin, 'get_settings_routes'):
|
||||
continue
|
||||
# One broken plugin must not take every settings hub down with it.
|
||||
try:
|
||||
plugin_routes = plugin.get_settings_routes()
|
||||
if plugin_routes:
|
||||
routes.extend(plugin_routes)
|
||||
|
||||
except Exception:
|
||||
current_app.logger.exception(
|
||||
'get_settings_routes failed for plugin %s; skipping it',
|
||||
plugin_name)
|
||||
continue
|
||||
routes.extend(renderable_settings_routes(plugin_name, plugin_routes,
|
||||
current_app.logger))
|
||||
|
||||
return success_response({'routes': routes})
|
||||
```
|
||||
|
||||
Earlier revisions of this draft showed a `@jwt_required(optional=True)` handler
|
||||
iterating `pm.get_enabled_plugin_instances()`. Neither is in the code:
|
||||
`grep -rn get_enabled_plugin_instances shopdb/ plugins/` returns nothing, so
|
||||
that block would not have run. It is replaced above rather than kept, because a
|
||||
copy-paste block that reopens the anonymous read is worse than no block.
|
||||
|
||||
**Auth:** the endpoint is gated on `settings.view`. It was written anonymous, on the reasoning that unauthenticated pages (kiosks, TV displays) register plugin tabs before login. Nothing does: its only caller is `ModelCatalogHub.vue`, which sits behind `requiresAuth` and `requiresAdmin`. Open, it handed an anonymous caller the enabled plugin list, the settings hub layout and the internal Vue component paths, so it was closed.
|
||||
|
||||
### Frontend: Dynamic Route Registration
|
||||
@@ -323,7 +345,7 @@ const pluginComponents = import.meta.glob('../.plugins-staged/*/views/*.vue')
|
||||
|
||||
**Nested routes:** A plugin could return routes with `children: [...]` for nested settings pages. Router supports this via `addRoute(parentName, childRoute)`.
|
||||
|
||||
**Permissions:** The `tab` could include a `permission` field (e.g., `"computers.manage_os"`), and the frontend filters out tabs the user can't access. The backend endpoint is anonymous, so permission checks would be client-side only (acceptable for UI gating; the API endpoint enforces permission anyway).
|
||||
**Permissions:** The `tab` could include a `permission` field (e.g., `"computers.manage_os"`), and the frontend would filter out tabs the user cannot reach. This would be UI gating only, and the page behind the tab must still enforce its own permission; the `settings.view` gate on `/api/plugins/settings-routes` says who may read the tab list, not who may use a given tab.
|
||||
|
||||
**Icons:** Add an `icon` field to the route metadata (e.g., `"mdi-microsoft-windows"`), and the tab renders the icon next to the label.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user