Ports the corporate-GitHub wilm branch onto main, keeping the two branches
separate. Thirteen upstream commits arrive with their authorship intact; the
review fixes and the portability work below are on top.
What arrives:
endpoints plugin - a ShopDB agent reporting endpoint state, telemetry,
services and interfaces, with a command channel the agent
long-polls. Ingest stays on the generic collector route
(ADR-006); the agent key is a UI-managed single-scope
token whose revocation is an instant kill switch.
computers policies - Configuration and Business Policies (RMM-style), scope
trees validated at write time, actions checked against
the endpoints command allowlist. Feature-gated: every
route 404s while the setting is off, so enabling it can
never half-expose the surface.
asset CSV import - templates, a collector script carrying the site path, and
a validate-then-commit import. A plugin can own a virtual
column plus a post-write row hook, which is how a PC's
machine number becomes a controls link without a
hand-built relationships sheet.
agent/ - the Windows agent and its installer.
Contract 0.21.0 -> 0.24.0, additive throughout: register_import_hook (0.22),
mint/list/revoke_service_token (0.23), collector_authorized (0.24). The stability
table had no rows for any of them and now does.
MySQL 5.6 portability. The branch stored three policy columns as sa.JSON, the
only JSON columns in the product, and 5.6 has no JSON type: the migration failed
with a bare syntax error near the word JSON. They are TEXT now, through a
JSONText type decorator, so all seventeen call sites still read and write dicts.
The convention elsewhere is already TEXT (guides.blocksjson,
geenforce.manifestjson).
Review fixes carried on top:
- six policy GETs were jwt_required(optional=True), which authenticates a
caller without checking anything; they require computers.view now, and six
redundant jwt_required decorators are gone
- the agent installer took a default BaseUrl and CollectorKey, so a mistyped
run silently pointed an endpoint at the wrong instance; both are mandatory
and a non-https BaseUrl throws unless -AllowInsecureHttp is passed
- a committing CSV import wrote no audit row
Documentation: the 34 new routes are described in docs/api-inventory.json, which
is hand-maintained on purpose, and the generated spec now covers 331 paths and
477 operations across 22 surfaces. DEVELOPMENT-SETUP's 1071 troubleshooting row
said "use MySQL 8 for dev" and now gives the actual fix, including the trap that
5.6 downgrades ROW_FORMAT=DYNAMIC to Compact with a warning instead of an error.
Full suite 2123 passed, vitest 260 passed, all seventeen plugin chains migrate
clean on MySQL 5.6 and the naming, docs, parity and read-surface gates are green.
141 lines
13 KiB
Markdown
141 lines
13 KiB
Markdown
# Path to contract 1.0
|
|
|
|
This page is an honest read of how stable the plugin contract is today, for a
|
|
sister site deciding how much to build on it. It is derived from the ADRs and
|
|
the live code, not aspiration. The authoritative hook reference is
|
|
[PLUGIN-HOOKS.md](PLUGIN-HOOKS.md); the versioning rules are in
|
|
[ADR-002](adr/ADR-002-plugin-versioning.md).
|
|
|
|
## Current version
|
|
|
|
The plugin contract version is declared in `shopdb/__init__.py` as
|
|
`__contract_version__`, and the current value is in
|
|
[PROJECT-MAP.md](PROJECT-MAP.md), which is generated. It is pre-1.0, which under
|
|
semver means any 0.x minor bump is allowed to break the contract, and this
|
|
project has used that latitude - see 0.19.0 below.
|
|
|
|
The product release version (`__version__`) is a separate series with its own
|
|
bump rules; see [ADR-007](adr/ADR-007-product-versioning-and-releases.md).
|
|
Do not pin against it for compatibility - pin against `__contract_version__`.
|
|
|
|
### 0.x history
|
|
|
|
Recorded in the comment block in `shopdb/__init__.py`:
|
|
|
|
| Version | Change | Kind |
|
|
|---------|--------|------|
|
|
| 0.3.0 | `shopdb.api` expanded to the full plugin import surface (db, cache, model bases, core models, response + pagination helpers, `employee_connection`) so plugins stop importing internal core paths | additive (minor) |
|
|
| 0.4.0 | Removed the never-implemented `get_searchable_fields` hook (search is a core concern over the asset model) and wired `get_dashboard_widgets` to a real consumer (`/api/dashboard/widgets`) | pre-1.0 contract reduction |
|
|
| 0.6.0 | Added the `get_reports` hook, consumed by `GET /api/reports` to merge plugin report cards into the Reports hub | additive optional hook (minor) |
|
|
| 0.7.0 | Added the four ADR-010 frontend-contribution hooks (`get_settings_cards`, `get_asset_panels`, `get_map_overlays`, `get_asset_presentation`), consumed by the `GET /api/pluginui/*` endpoints | additive optional hooks (minor) |
|
|
| 0.9.0 | Exposed the dualpath pair-resolution helpers on `shopdb.api` for the machines plugin | additive surface (minor) |
|
|
| 0.10.0 | Added the `get_permissions` hook so plugins declare their own RBAC permissions; the catalog is resolved dynamically from core + enabled plugins | additive optional hook (minor) |
|
|
| 0.11.0 | Added `service_token_authorized(scope)` to `shopdb.api` so a plugin's unattended endpoints (e.g. the GE-Enforce fetch API) can authorize a scoped managed service token without importing core token internals | additive surface (minor) |
|
|
| 0.12.0 | Added the mailer helpers (`send_email`, `send_alert`) to `shopdb.api` | additive surface (minor) |
|
|
| 0.13.0 | Added the `User` model to the `shopdb.api` surface | additive surface (minor) |
|
|
| 0.14.0 | Added `send_webhook` to the `shopdb.api` surface | additive surface (minor) |
|
|
| 0.15.0 | Added `authorized_service_token` / the `SupportTeam` model to the `shopdb.api` surface | additive surface (minor) |
|
|
| 0.16.0 | Added the `get_settings_defaults` hook so a plugin declares the Setting rows it owns; the framework seeds them at install, at enable, and on `flask plugin upgrade-all`, files a first-time write under the declared category, and honours `public: True` for pages that render before login | additive optional hook (minor) |
|
|
| 0.17.0 | Added `DashboardDefault` to the `shopdb.api` surface, so a plugin can resolve a display without reaching into core | additive surface (minor) |
|
|
| 0.18.0 | Added `DISPLAY_ROLES`, `DISPLAY_ROLE_PATHS` and `normalize_display_role`, and finally exported `DashboardDefault`, which 0.17.0 imported but left out of `__all__`. The role vocabulary became the kiosk's own - `Dashboard`, `Lobby`, `3DPrintRoom` - so a plugin holding its own copy of that map reads core's instead of drifting from it | additive surface (minor) |
|
|
| 0.19.0 | **BREAKING.** `get_dashboard_widgets` returns DATA AND SHAPE, not a component name. The old shape (`name` + `component` + `size`) named a Vue component per widget, which cannot survive a lean build - a plugin's component may never be staged into the frontend bundle (ADR-013) - and five plugins were declaring widgets that pointed at components nobody had written. A card now declares `id` / `title` / `endpoint` / `render` / `severity` / `permission` / `empty` / `position`; see PLUGIN-HOOKS.md. Also added `send_upload` so a plugin serving user-supplied bytes gets the headers that keep an SVG from running as script | **contract change (minor, pre-1.0)** |
|
|
| 0.20.0 | The `Asset` model gained `levelid` (which floor plan its `mapx`/`mapy` are pixels of) and `mapverifiedat`; `Location` gained `levelid` too, and `resolve_asset_position` now returns the `levelid` belonging to whichever source supplied the coordinates. A plugin that writes a map position MUST write the level with it - a position without one renders as "level unknown" rather than being drawn on the default drawing, because a marker on the wrong floor plan looks correct (ADR-017) | additive surface (minor) |
|
|
| 0.21.0 | Added the `get_guest_grantable_permissions` hook: a plugin names the WRITE permissions a site may grant to the seeded `guest` role, which is who anyone without a token now holds. Reads (`.view`/`.read`) are grantable without a plugin saying anything; anything else is refused. Also exposed `require_any_permission` on `shopdb.api` for a read two different rights should reach | additive optional hook + surface (minor) |
|
|
| 0.22.0 | Added the `register_import_hook` surface: a plugin owns a VIRTUAL COLUMN on an importable CSV table - a header the bulk importer accepts but never writes - plus a post-write row hook that turns the value into a plugin concept. The computers plugin uses it for `machineassetnumber` on `computers.csv`, which builds the PC->machine controls link, so a site writes the machine number in the PC's own row instead of hand-building a separate relationships sheet. Core never learns what the column means. `RowWarning` is exported with it so a hook can report a soft failure without aborting the import | additive surface (minor) |
|
|
| 0.23.0 | Added `mint_service_token`, `list_service_tokens` and `revoke_service_tokens` so a plugin can provision, list and revoke a single-scope managed service token without importing core token internals. The endpoints plugin uses them for a UI-managed, regenerable agent key scoped to `collector.ingest.endpoints` alone - narrow on purpose, so rotating or revoking it cannot disturb the computers or GE-Enforce collectors, and revocation is an instant kill switch on agent ingest | additive surface (minor) |
|
|
| 0.24.0 | Added `collector_authorized(pluginname)` so a plugin can gate an agent-facing route that lives OUTSIDE the generic collector blueprint on the same per-plugin key and kill switch the ingest route uses. The endpoints command channel needs it: the agent long-polls `/api/endpoints/commands/next` and posts results back, and those are agent callbacks rather than JWT routes. Without it a plugin would reach into core collector internals to authorize them | additive surface (minor) |
|
|
|
|
The source comment block documents 0.3.0, 0.4.0, 0.6.0, 0.7.0, 0.9.0, 0.10.0,
|
|
0.11.0, 0.16.0, 0.18.0 and 0.19.0; 0.12.0 through 0.15.0 and 0.17.0 are recorded
|
|
in this table only.
|
|
|
|
**0.19.0 is the one to read before pinning.** It is the only entry in this
|
|
series that took something away, and it shipped before it was recorded here -
|
|
which is precisely the reason to pin tight rather than to trust that a minor
|
|
bump is safe. Earlier points
|
|
(0.1.x / 0.2.x) predate that recorded rationale; `PluginMeta`'s fallback
|
|
`core_version` default of `>=0.2.0,<1.0.0` is the only remaining trace of the
|
|
0.2 baseline.
|
|
|
|
## Settled surface
|
|
|
|
These are unlikely to break before 1.0. A plugin can depend on them with
|
|
reasonable confidence; a breaking change to any would be a major bump and would
|
|
land with a new or amended ADR.
|
|
|
|
| Surface | What it is |
|
|
|---------|-----------|
|
|
| `meta` / manifest schema | `PluginMeta` fields and `manifest.json` (ADR-002 single source of truth) |
|
|
| `get_blueprint` | Flask Blueprint registration at the manifest `api_prefix` |
|
|
| `get_models` | SQLAlchemy model classes the plugin owns |
|
|
| `init_app` | Custom init after blueprint + models are known |
|
|
| `get_cli_commands` | Click commands added to the Flask CLI |
|
|
| `get_services` | Named service classes, consumed by `plugin_manager.get_service` |
|
|
| Lifecycle hooks | `on_install`, `on_uninstall`, `on_enable`, `on_disable` |
|
|
| `get_navigation_items` | Sidebar menu entries |
|
|
| `get_dashboard_widgets` | Dashboard widgets, consumed by `/api/dashboard/widgets` |
|
|
| `get_reports` | Report cards, consumed by `/api/reports` (added 0.6.0) |
|
|
| `get_permissions` | Plugin RBAC permissions, merged into the catalog for roles and token scopes (added 0.10.0) |
|
|
| Frontend-contribution hooks | `get_settings_cards`, `get_asset_panels`, `get_map_overlays`, `get_asset_presentation`, consumed by `/api/pluginui/*` (added 0.7.0, [ADR-010](adr/ADR-010-frontend-plugin-hooks.md)) |
|
|
| Collector pair | `get_collector_schema` + `apply_collector_payload` per [ADR-006](adr/ADR-006-collector-contract.md) |
|
|
| Settings helpers | `get_setting` / `set_setting`, namespaced to the plugin |
|
|
| `get_provisioning_note` | Setup-wizard transparency note for extra tables |
|
|
| `get_config_schema` | Setup-wizard config field declarations |
|
|
| `shopdb.api` import surface | The only core module plugins may import (plus `shopdb.plugins.base`); adding a name is minor, removing one is major |
|
|
|
|
The asset model itself - `Asset`, `AssetType`, `AssetStatus`,
|
|
`AssetRelationship`, and the shared reference models - is the platform contract
|
|
locked in [ADR-001](adr/ADR-001-asset-as-platform-contract.md).
|
|
|
|
## Expected churn before 1.0
|
|
|
|
Known-unstable areas. Building on these means expecting rework.
|
|
|
|
| Area | Status | Reference |
|
|
|------|--------|-----------|
|
|
| Frontend renderers (residual) | The four data-only hooks and their `/api/pluginui/*` consumers are settled (0.7.0). The generic core renderers are landing incrementally: the settings-cards rail/landing renderer ships with 0.7.0; the asset-panel, map-overlay, and search-presentation renderers are wired opt-in per the ADR adoption plan. Real component-backed panels (bespoke charts, custom overlays) remain deferred to Option C (build-time glob discovery). | [ADR-010](adr/ADR-010-frontend-plugin-hooks.md) (ACCEPTED) |
|
|
| Per-plugin migrations | Brand new. The per-plugin Alembic engine exists and every bundled plugin now carries a chain, but the pattern has one release of production mileage, not years. | [ADR-008](adr/ADR-008-plugin-migration-ownership.md) (2026-07-10) |
|
|
| Pip distribution | Deferred to v2. External plugins install by clone / submodule / symlink; there is no entry-point discovery and no automatic update path yet. | [ADR-003](adr/ADR-003-plugin-distribution.md) |
|
|
|
|
## Bump rules
|
|
|
|
From [ADR-002](adr/ADR-002-plugin-versioning.md), applied to `__contract_version__`:
|
|
|
|
| Bump | Trigger |
|
|
|------|---------|
|
|
| major | Breaking change to the `BasePlugin` ABC, the `PluginMeta` schema, or any model in the platform contract (`Asset`, `AssetType`, `AssetStatus`, `AssetRelationship`, `Vendor`, `Location`, `BusinessUnit`, `Model`, `OperatingSystem`). Removing a name from `shopdb.api` is major. |
|
|
| minor | Additive change: a new optional hook, a new field on a contract model with a default, a new name added to `shopdb.api`. |
|
|
| patch | Bug fix with no change to the contract surface. |
|
|
|
|
### Deprecation policy
|
|
|
|
ADR-002 defines the bump classification above but is **silent** on any
|
|
deprecation window or notice period for pre-1.0 removals. In practice a removal
|
|
is simply a major (or, pre-1.0, a breaking minor) bump: the hook or name is
|
|
gone, and the loader fails loud in dev or excludes the mismatched plugin in prod
|
|
(see the load-time table in [PLUGIN-EXTERNAL-REPO.md](PLUGIN-EXTERNAL-REPO.md)).
|
|
Because the ADR says nothing about a grace period, this document proposes none;
|
|
the honest guidance for sister sites is the mitigation that already exists - pin
|
|
a tight `core_version` range and re-test before widening it.
|
|
|
|
## Criteria for declaring 1.0
|
|
|
|
Provisional. This is the maintainer's working list, not a committed checklist,
|
|
and it will move. ADR-002's own open question ("When does the framework declare
|
|
1.0.0?") ties 1.0 to the `Machine` retirement from ADR-001 and the framework
|
|
being "ready for sister sites"; the items below expand that intent.
|
|
|
|
- Frontend hook contract defined via its own ADR (asset-detail panels, map
|
|
markers, search results), closing the biggest churn item above.
|
|
- `Machine` / legacy-model retirement complete per ADR-001, so the asset model
|
|
is the only contract.
|
|
- At least two external plugins running in production at a second site - the
|
|
bar ADR-003 already sets before pip distribution and sister-site readiness
|
|
are considered justified.
|
|
- The per-plugin migration pattern (ADR-008) proven across real upgrades, not
|
|
just fresh installs.
|
|
- No contract bump needed for N consecutive product releases (N to be fixed
|
|
when the list is firmed up), showing the surface has actually settled.
|
|
|
|
Until then: pre-1.0, pin tight, re-test on every framework bump.
|