docs: wiki staleness sweep (Fable-orchestrated Opus audit)
Audited all 40 docs/ against the live codebase; fixed factual staleness in 23, 14 were clean. Highlights (all verified against code): - equipment -> machines (ADR-011 rename) in INSTALL/DEPLOY-WINDOWS-IIS, PLUGIN-GUIDE, GE-ENFORCE, ROADMAP. - Versions refreshed: contract 0.10.0 -> 0.13.0, product 0.5.0 -> 0.7.0, plus plugin example core_version pins. - Bundled set corrected to the current 13 (PLUGINS.md 7 -> 13 rows; DEPLOY eleven -> thirteen). - Per-plugin Alembic chain workflow (ADR-008) replacing stale core-chain steps in PLUGIN-QUICKSTART / BACKUP-RESTORE; deploy adds plugin upgrade-all. - Frontend plugin staging (ADR-010) replacing 'no frontend plugin system yet' in PLUGIN-GUIDE; view/route paths repointed to plugins/<name>/frontend/. - Corrected file paths (MapView.vue, manifest_schema.json), CLI (shelf-list), API gating (GET /api/plugins is optional-jwt), WJF 15 -> 16 stages, and retired Collector/PC-Types settings pages (ADR-012). - ge-enforce proposal marked ACCEPTED/built.
This commit is contained in:
@@ -96,18 +96,42 @@ CREATE DATABASE shopdb_flask CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
tar xzf instance-2026-07-10.tar.gz # restores ./instance/
|
||||
```
|
||||
|
||||
The docker-compose api container reads `instance/` from the repo working
|
||||
directory; make sure it is present before starting `api`.
|
||||
The default `docker-compose.yml` does NOT bind-mount `instance/` into the api
|
||||
container (its only volume is `- ./plugins:/app/plugins:ro`, and the image never
|
||||
copies `instance/`), so the container's Flask instance path is an empty
|
||||
`/app/instance` and a restored host `./instance` is invisible to it. To make the
|
||||
restored `instance/` visible, add a bind mount to the api service before starting
|
||||
it:
|
||||
|
||||
```yaml
|
||||
api:
|
||||
volumes:
|
||||
- ./plugins:/app/plugins:ro
|
||||
- ./instance:/app/instance
|
||||
```
|
||||
|
||||
Make sure `./instance` is present on the host before starting `api`.
|
||||
|
||||
### Step 4: Bring up the API and reconcile migrations
|
||||
|
||||
```bash
|
||||
docker compose up -d api
|
||||
docker compose exec api flask db upgrade
|
||||
docker compose exec api flask plugin upgrade-all
|
||||
```
|
||||
|
||||
For a non-docker deploy:
|
||||
|
||||
```bash
|
||||
flask db upgrade
|
||||
flask plugin upgrade-all
|
||||
```
|
||||
|
||||
`flask db upgrade` is a safety net: if the dump predates the current code, this
|
||||
applies any newer migrations. If the dump is at the same version it is a no-op.
|
||||
applies only the core Alembic chain. `flask plugin upgrade-all` then applies any
|
||||
newer per-plugin migrations (each bundled plugin owns its own chain, ADR-008);
|
||||
without it, plugin-owned tables stay un-migrated. If the dump is at the same
|
||||
version both are no-ops.
|
||||
|
||||
### Step 5: Verify
|
||||
|
||||
|
||||
@@ -78,9 +78,10 @@ suspended for it, so the token is contained to the collector API even though its
|
||||
owner is an admin - it cannot act with admin authority anywhere.
|
||||
|
||||
1. Settings > API Tokens > New Token.
|
||||
2. Click the **Collector service token** preset (pre-selects only
|
||||
`collector.ingest`), name it (e.g. `wj-fleet-collector`), optionally set an
|
||||
expiry, Create.
|
||||
2. Check **Restrict permissions**, then in the permissions grid tick only
|
||||
**Submit collector payloads (fleet reporting)** (the `collector.ingest`
|
||||
permission under the Collector category). Name it (e.g. `wj-fleet-collector`),
|
||||
optionally set an expiry, Create.
|
||||
3. Copy the `shopdb_pat_...` secret (shown once) and deploy it to the fleet the
|
||||
same way as the env key: the `collectorApiKey` field in per-site
|
||||
`site-config.json` (see "Delivering the API key to clients" below). The
|
||||
@@ -244,10 +245,12 @@ the PCs that use it (both render in the shared Relationships card).
|
||||
### pc-type mapping (configurable per site)
|
||||
|
||||
`pctype` (e.g. `gea-shopfloor-cmm`) maps to a shopdb Computer Type through
|
||||
`pctypemap_<pxetype>` settings (Settings > Collector PC Types).
|
||||
Defaults live in `plugins/computers/pctypemap.py` and are seeded on plugin
|
||||
install; edit per site in the UI. Unmapped pc-types produce a warning, not a
|
||||
failure.
|
||||
`pctypemap_<pxetype>` settings. The "Collector PC Types" settings page is
|
||||
retired (ADR-012): pc-type-to-Computer-Type handling now lives in GE-Enforce
|
||||
(each imaging PC type is a manifest scope with its own `computertypeid`). The
|
||||
built-in defaults in `plugins/computers/pctypemap.py` are still seeded on plugin
|
||||
install and the collector still reads them, so existing enrollment keeps
|
||||
working. Unmapped pc-types produce a warning, not a failure.
|
||||
|
||||
### Classic api.asp field mapping (for porting the PowerShell reporter)
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ the live code, not aspiration. The authoritative hook reference is
|
||||
|
||||
## Current version
|
||||
|
||||
The plugin contract is at **0.10.0**, declared in `shopdb/__init__.py` as
|
||||
The plugin contract is at **0.13.0**, declared in `shopdb/__init__.py` as
|
||||
`__contract_version__`. 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.
|
||||
|
||||
The product release version (`__version__`, currently 0.5.0) is a separate
|
||||
The product release version (`__version__`, currently 0.7.0) 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__`.
|
||||
|
||||
@@ -28,8 +28,13 @@ Recorded in the comment block in `shopdb/__init__.py`:
|
||||
| 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) |
|
||||
|
||||
The source comment block documents 0.3.0, 0.4.0, 0.6.0, 0.7.0, 0.9.0, and 0.10.0. Earlier points
|
||||
The source comment block documents 0.3.0, 0.4.0, 0.6.0, 0.7.0, 0.9.0, 0.10.0, and 0.11.0 (its
|
||||
last entry); the current `__contract_version__` 0.13.0 is ahead of the last documented comment
|
||||
entry. 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.
|
||||
|
||||
@@ -19,7 +19,7 @@ physical path must be `APP_ROOT` (where `wsgi.py` lives).
|
||||
- **URL Rewrite** module (only for the optional real-client-IP rule).
|
||||
- Network access to the MySQL 5.6 server.
|
||||
- If the box is air-gapped, you cannot `pip install` live. On the dev box run
|
||||
`pip download -r requirements.txt waitress -d wheels\` (on a matching
|
||||
`pip download -r requirements.txt -d wheels\` (on a matching
|
||||
Windows/Python target, or use `--platform` wheels), copy `wheels\` over, and
|
||||
install with `pip install --no-index --find-links wheels\ ...`.
|
||||
|
||||
@@ -42,12 +42,11 @@ cd C:\shopdb-flask
|
||||
py -3.12 -m venv venv
|
||||
venv\Scripts\python -m pip install --upgrade pip
|
||||
venv\Scripts\pip install -r requirements.txt
|
||||
venv\Scripts\pip install waitress
|
||||
```
|
||||
|
||||
The DB driver is `pymysql` (pure Python) so no C compiler / MySQL client libs
|
||||
are needed. `waitress` is the WSGI server (installed separately, same as the
|
||||
Docker image installs gunicorn separately).
|
||||
are needed. `waitress` is the WSGI server and ships in `requirements.txt`
|
||||
(unlike gunicorn, which the Docker image installs separately).
|
||||
|
||||
## 3. Prepare MySQL 5.6 (the utf8mb4 gotcha)
|
||||
|
||||
|
||||
@@ -116,13 +116,15 @@ docker compose exec api flask seed reference-data
|
||||
- `seed settings` - writes the default Setting rows (branding, ServiceNow
|
||||
integration, floor-map placeholders, search toggles, site identity). A site
|
||||
overrides these later in Settings or the setup wizard.
|
||||
- `seed reference-data` - creates default `Vendor`, `Location`, `BusinessUnit`,
|
||||
`OperatingSystem`, `AssetStatus`, `RelationshipType` rows seeded with the
|
||||
platform contract values (`partof`, `controls`, `connectedto`).
|
||||
- `seed reference-data` - creates default `ModelType`, `AssetStatus`,
|
||||
`LocationType`, `CommunicationType`, `OperatingSystem`, `RelationshipType` rows
|
||||
seeded with the platform contract values (`partof`, `controls`, `connectedto`).
|
||||
(`Vendor`, `Location`, and `BusinessUnit` are not seeded here; they come from
|
||||
`seed demo`.)
|
||||
|
||||
## Step 5: Pick plugins to enable
|
||||
|
||||
The image bundles eleven plugins (computers, employees, knowledgebase, machines, measuringtools, network, notifications, printers, slides, usb, warranty). Only enabled plugins are loaded.
|
||||
The image bundles thirteen plugins (computers, employees, geenforce, knowledgebase, machines, measuringtools, network, notifications, printedparts, printers, slides, usb, warranty). Only enabled plugins are loaded.
|
||||
|
||||
```bash
|
||||
docker compose exec api flask plugin list
|
||||
|
||||
@@ -23,7 +23,7 @@ sane, then use manual for day-to-day work.
|
||||
|
||||
| Need | Version | Check |
|
||||
| --- | --- | --- |
|
||||
| Python | 3.13 (64-bit) - matches prod + CI | `python --version` |
|
||||
| Python | 3.13 (64-bit) - matches CI; prod images run 3.12 | `python --version` |
|
||||
| Node.js | 18+ | `node --version` |
|
||||
| MySQL | 8.0 (or Docker, below) | `mysql --version` |
|
||||
| Git | any recent | `git --version` |
|
||||
|
||||
@@ -88,7 +88,7 @@ POST /api/geenforce/report
|
||||
present), `skipped` (detected present), `failed`, `filtered`. `selfhealed`
|
||||
marks a drift correction.
|
||||
- shopdb keeps the latest report per (hostname, scope, phase) plus history, and
|
||||
surfaces it under Settings > Enforcement Reports.
|
||||
surfaces it under GE-Enforce > Enforcement Reports.
|
||||
|
||||
The engine already computes these counts (`installed/skipped/failed/pcFiltered`
|
||||
at the end of its main loop) and knows each entry's action; shape them into the
|
||||
@@ -105,7 +105,7 @@ with `Installed/Skipped/Failed/Filtered` + a `Results` list).
|
||||
3. **Read cutover**: drop `-ShadowMode`. The engine now runs against the
|
||||
shopdb-sourced manifest; payloads still come from the share. Rollback is a
|
||||
one-line revert to the share-sourced call. Keep exporting manifests from
|
||||
shopdb to the share (Settings > Imaging PC Types > Export to Share) so the
|
||||
shopdb to the share (GE-Enforce > Manifests > Export to Share) so the
|
||||
share stays a break-glass copy.
|
||||
4. **Payload migration** (optional, later): move small scripts/configs to
|
||||
`http`/`inline` payloads, verified by `payloadsha256`. Big MSIs stay on SMB.
|
||||
|
||||
@@ -231,9 +231,9 @@ because it is a full management surface.
|
||||
### 4.1 Manifests - authoring (GE-Enforce > Manifests)
|
||||
|
||||
- **PC Types (scopes):** each imaging PC type is a row; add/edit/delete. (The
|
||||
scope carries an optional `computertypeid` reference field, but the collector's
|
||||
imaging-pc-type -> ComputerType mapping is configured separately at
|
||||
Settings > Collector PC Types.)
|
||||
scope's `computertypeid` field is now the imaging-pc-type -> ComputerType
|
||||
mapping mechanism, per ADR-012. The old Settings > Collector PC Types page is
|
||||
retired; there is nothing to configure there anymore.)
|
||||
- **Entries:** an ordered list (Up/Down = the execution-order contract). Add/Edit
|
||||
opens a typed form: the payload fields switch on `Type` (MSI shows Installer +
|
||||
InstallArgs, PS1 shows Script + Args, File shows Source + Destination, Registry
|
||||
|
||||
@@ -113,7 +113,7 @@ venv\Scripts\flask seed settings
|
||||
# enable the plugins this site tracks (registry is empty on a fresh box).
|
||||
# usb + employees install DISABLED by default - enable them later in the wizard
|
||||
# if the site wants those (they create extra tables).
|
||||
foreach ($p in "computers","equipment","network","notifications","printers","knowledgebase","slides","warranty") {
|
||||
foreach ($p in "computers","machines","network","notifications","printers","knowledgebase","slides","warranty") {
|
||||
venv\Scripts\flask plugin install $p
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ each gets its own site, app pool, port, and venv.
|
||||
| **500** with an empty HttpPlatform log | app-pool identity can't read `APP_ROOT` / run the venv (step 7.3), or `.env` missing/invalid. |
|
||||
| "internal error" toggling plugins, or uploads fail | app pool cannot WRITE `APP_ROOT\instance` (plugin registry, logos, photos, files live there) - step 7.3 grants it Modify. |
|
||||
| "No time zone found with key America/New_York" | `tzdata` not installed (`pip install tzdata`). |
|
||||
| Nav missing Equipment/PCs/... | plugins not installed (step 6 `flask plugin install`), or site not recycled. |
|
||||
| Nav missing Machines/PCs/... | plugins not installed (step 6 `flask plugin install`), or site not recycled. |
|
||||
| Method B: blank page / assets 404 under `/ops` | frontend `dist` built without `VITE_BASE_PATH=/ops/` (step 7b.1). |
|
||||
| Method B: SPA loads but every API call 404s | `MOUNT_PATH` unset or not matching the Application alias (step 7b.3). |
|
||||
| ConfigError on boot | a required `.env` var missing or left at a dev default. |
|
||||
|
||||
@@ -76,7 +76,7 @@ import API. It is site glue, not product code.
|
||||
venv/bin/python -m scripts.site_imports.wjf.run
|
||||
```
|
||||
|
||||
The 15 stages run in order (reference -> catalog -> assets hub -> locations ->
|
||||
The 16 stages run in order (reference -> catalog -> assets hub -> locations ->
|
||||
printers -> dependents -> relationships -> subnets -> usb -> verify). It is
|
||||
idempotent - a crashed run resumes from `idmap.json`.
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ admits only the contract minor you tested against, not the whole 0.x line.
|
||||
The current contract version is declared in `shopdb/__init__.py`:
|
||||
|
||||
```python
|
||||
__contract_version__ = '0.6.0'
|
||||
__contract_version__ = '0.13.0'
|
||||
```
|
||||
|
||||
Recommended pin in your `manifest.json`, per ADR-002 (pip-style `>=,<` ranges):
|
||||
@@ -122,7 +122,7 @@ Recommended pin in your `manifest.json`, per ADR-002 (pip-style `>=,<` ranges):
|
||||
"name": "shipping",
|
||||
"version": "1.0.0",
|
||||
"description": "Tracks shipping-station scanners and label printers",
|
||||
"core_version": ">=0.6.0,<0.7.0",
|
||||
"core_version": ">=0.13.0,<0.14.0",
|
||||
"dependencies": []
|
||||
}
|
||||
```
|
||||
|
||||
@@ -78,7 +78,7 @@ plugin's identity (ADR-002):
|
||||
Two fields deserve attention.
|
||||
|
||||
`core_version` is a semver range against the framework's `__contract_version__`
|
||||
(declared in `shopdb/__init__.py`, currently `0.6.0`). The loader refuses to load
|
||||
(declared in `shopdb/__init__.py`, currently `0.13.0`). The loader refuses to load
|
||||
a plugin whose range excludes the running framework. We pin `>=0.6.0` because this
|
||||
plugin uses the `get_reports` hook, which was added to the contract in 0.6.0
|
||||
(see [PLUGIN-HOOKS.md](PLUGIN-HOOKS.md), "get_reports"). We cap at `<1.0.0` because
|
||||
@@ -272,7 +272,7 @@ assertion to a frozen `CUTOVER_PLUGINS` list rather than to all discovered plugi
|
||||
and to expect `measuringtools`'s real baseline revision in the upgrade-all test:
|
||||
|
||||
```python
|
||||
CUTOVER_PLUGINS = ('computers', 'employees', 'equipment', 'knowledgebase',
|
||||
CUTOVER_PLUGINS = ('computers', 'employees', 'knowledgebase', 'machines',
|
||||
'network', 'notifications', 'printers', 'slides', 'usb', 'warranty')
|
||||
|
||||
@pytest.mark.parametrize('plugin', CUTOVER_PLUGINS) # not all plugins
|
||||
@@ -281,7 +281,10 @@ def test_anchor_migration_is_noop(plugin):
|
||||
```
|
||||
|
||||
Freezing the list (rather than deriving it) is intentional: a newly discovered
|
||||
plugin should not silently be treated as a cutover no-op.
|
||||
plugin should not silently be treated as a cutover no-op. Note that `machines`
|
||||
(renamed from `equipment`, ADR-011) keeps its original cutover anchor but carries
|
||||
a `machines0002rename` revision on top of it, so its expected head is that rename
|
||||
revision rather than the bare anchor (`tests/test_plugin_migrations.py`).
|
||||
|
||||
---
|
||||
|
||||
@@ -499,23 +502,25 @@ the plugin small.
|
||||
|
||||
## 9. Frontend integration
|
||||
|
||||
There is no frontend plugin system yet (see
|
||||
[ADR-009](adr/ADR-009-frontend-plugin-gating.md), "Future direction"). A plugin's
|
||||
Vue routes and views ship in the core bundle. The plugin's job is to add them
|
||||
correctly and gate them.
|
||||
A plugin ships its own Vue routes and views under
|
||||
`plugins/<name>/frontend/` (ADR-010's frontend plugin hook contract). At build
|
||||
time those files are staged into `frontend/src/.plugins-staged/<name>/` so the
|
||||
core bundle picks them up; you author them in the plugin tree, not in core
|
||||
`frontend/src/`. The plugin's job is to add them correctly and gate them.
|
||||
|
||||
**Route module with `meta.plugin` gating (ADR-009).** A new file
|
||||
`frontend/src/router/routes/measuringtools.js` is auto-discovered by the router's
|
||||
**Route module with `meta.plugin` gating (ADR-009).** The file
|
||||
`plugins/measuringtools/frontend/routes.js` is staged into
|
||||
`frontend/src/.plugins-staged/measuringtools/` and auto-discovered by the router's
|
||||
`import.meta.glob('./routes/*.js')`. Every route carries `meta.plugin =
|
||||
'measuringtools'`:
|
||||
|
||||
```js
|
||||
export default [
|
||||
{ path: 'measuringtools', name: 'measuringtools',
|
||||
component: () => import('../../views/measuringtools/MeasuringToolsList.vue'),
|
||||
component: () => import('./views/MeasuringToolsList.vue'),
|
||||
meta: { plugin: 'measuringtools' } },
|
||||
{ path: 'measuringtools/new', name: 'measuringtool-new',
|
||||
component: () => import('../../views/measuringtools/MeasuringToolForm.vue'),
|
||||
component: () => import('./views/MeasuringToolForm.vue'),
|
||||
meta: { requiresAuth: true, plugin: 'measuringtools' } },
|
||||
{ path: 'measuringtools/:id', ..., meta: { plugin: 'measuringtools' } },
|
||||
{ path: 'measuringtools/:id/edit', ..., meta: { requiresAuth: true, plugin: 'measuringtools' } },
|
||||
@@ -539,18 +544,18 @@ reorganize the file; just add the block, mirroring `machinesApi`.
|
||||
**Views mirror the master templates.** The frontend has master templates
|
||||
(`PrintersList.vue` for lists, `PrinterDetail.vue` for detail pages). `measuringtools` mirrors the equivalent equipment views:
|
||||
|
||||
- `views/measuringtools/MeasuringToolsList.vue` - table with search, a type filter,
|
||||
- `plugins/measuringtools/frontend/views/MeasuringToolsList.vue` - table with search, a type filter,
|
||||
and a calibration-status filter; the status badge uses `utils/colorStyle` with
|
||||
the color the API derived.
|
||||
- `views/measuringtools/MeasuringToolDetail.vue` - hero + Identity card +
|
||||
- `plugins/measuringtools/frontend/views/MeasuringToolDetail.vue` - hero + Identity card +
|
||||
Calibration card (with the derived badge) + Location card, plus the shared
|
||||
`CustomFieldsSection` and `WarrantyPanel` (section 10).
|
||||
- `views/measuringtools/MeasuringToolForm.vue` - asset core fields + type +
|
||||
- `plugins/measuringtools/frontend/views/MeasuringToolForm.vue` - asset core fields + type +
|
||||
location + the calibration fields, plus `CustomFieldsInputs`.
|
||||
- `views/reports/CalibrationReport.vue` - the four buckets (overdue / due soon /
|
||||
- `plugins/measuringtools/frontend/views/CalibrationReport.vue` - the four buckets (overdue / due soon /
|
||||
current / unknown), mirroring `WarrantyReport.vue`.
|
||||
|
||||
**Settings subtype page.** `views/settings/MeasuringToolTypesList.vue` mirrors
|
||||
**Settings subtype page.** `plugins/measuringtools/frontend/views/MeasuringToolTypesList.vue` mirrors
|
||||
`PCTypesList.vue`: add / edit / delete with a `ColorSwatchPicker`. It is linked from
|
||||
`settingsNav.js` with a "Measuring Tools" card group, so it appears in the settings
|
||||
rail and landing overview.
|
||||
@@ -578,7 +583,7 @@ detail page drops in `<CustomFieldsSection :assetid="tool.assetid" />` and the f
|
||||
drops in `<CustomFieldsInputs :assettypeid="assettypeid" :assetid="currentAssetId" />`,
|
||||
then calls `customFieldsRef.value.save(assetId)` after the tool saves. The one
|
||||
subtlety: `CustomFieldsInputs` needs the asset-type id. Rather than hardcode it (the
|
||||
equipment form hardcodes `EQUIPMENT_ASSETTYPEID = 1`), the measuringtools form
|
||||
machines form hardcodes `MACHINE_ASSETTYPEID = 1`), the measuringtools form
|
||||
resolves it dynamically from `GET /api/assets/types`, finding the row whose
|
||||
`assettype === 'measuring_tool'`. Dynamic lookup is preferred because seeded ids are
|
||||
not stable across sites.
|
||||
|
||||
@@ -487,7 +487,8 @@ What `shopdb.api` exposes:
|
||||
- Model bases: `BaseModel`, `AuditMixin`
|
||||
- Core models: `Asset`, `AssetType`, `AssetStatus`, `Vendor`, `Model`,
|
||||
`Communication`, `CommunicationType`, `Location`, `Setting`, `AuditLog`,
|
||||
`Application`, `AppVersion`, `OperatingSystem`
|
||||
`Application`, `AppVersion`, `OperatingSystem`, `AssetRelationship`,
|
||||
`RelationshipType`
|
||||
- Responses: `success_response`, `error_response`, `paginated_response`,
|
||||
`ErrorCodes`
|
||||
- Pagination: `get_pagination_params`, `paginate_query`
|
||||
@@ -501,6 +502,8 @@ What `shopdb.api` exposes:
|
||||
- Import mode: `apply_import_timestamps`, `import_mode_active`,
|
||||
`parse_import_datetime`
|
||||
- Legacy employee directory: `employee_connection`
|
||||
- CMMC USB check-in/out DB (read-write, used by the usb plugin):
|
||||
`cmmc_usb_connection`
|
||||
- `User` / `Role` (0.13.0) - the account and role models, e.g. resolving
|
||||
alert recipients' emails from selected user ids or role membership
|
||||
- Mailer (0.12.0): `send_email(to, subject, html, text=None)` and
|
||||
|
||||
@@ -21,7 +21,8 @@ decision records: `docs/proposals/printedparts-plugin.md`.
|
||||
|
||||
|
||||
Know before you start
|
||||
- BUNDLED plugin: frontend files live in core `frontend/src/`, and three core
|
||||
- BUNDLED plugin: frontend files live in `plugins/printedparts/frontend/`
|
||||
(staged into the Vite tree by `scripts/stage-frontend.mjs`), and three core
|
||||
files get small edits (api client, sidebar icon map, PLUGIN_TABLE_OWNERS).
|
||||
Normal for every bundled plugin.
|
||||
- Ground rules: import core ONLY via `shopdb.api` (+ `shopdb.plugins.base`);
|
||||
@@ -92,7 +93,7 @@ the edit (imports/meta boilerplate unchanged from the scaffold):
|
||||
"description": "3D-printed parts inventory + kiosk checkout",
|
||||
"display_name": "3D Printed Parts",
|
||||
"dependencies": ["employees"],
|
||||
"core_version": ">=0.13.0,<1.0.0",
|
||||
"core_version": ">=0.12.0,<1.0.0",
|
||||
"api_prefix": "/api/printedparts",
|
||||
"default_enabled": false
|
||||
}
|
||||
@@ -371,8 +372,8 @@ export const printedpartsApi = {
|
||||
```
|
||||
|
||||
2. Rename the scaffold views to `PrintedItemsList/PrintedItemDetail/
|
||||
PrintedItemForm.vue` and repoint `frontend/src/router/routes/printedparts.js`
|
||||
(auto-discovered by the router; list/detail carry `meta.plugin`, new/edit add
|
||||
PrintedItemForm.vue` and repoint `plugins/printedparts/frontend/routes.js`
|
||||
(aggregated into the router via `routes.gen.js`; list/detail carry `meta.plugin`, new/edit add
|
||||
`requiresAuth`).
|
||||
|
||||
3. The list page, core of `PrintedItemsList.vue` (master template:
|
||||
@@ -885,10 +886,12 @@ defineEmits(['digit', 'clear', 'backspace'])
|
||||
(Terminal-style CSS - fixed 3-column grid, big targets, press feedback - at
|
||||
the tag.)
|
||||
|
||||
### The kiosk view - `frontend/src/views/printedparts/PartsKiosk.vue`
|
||||
### The kiosk view - `plugins/printedparts/frontend/views/PartsKiosk.vue`
|
||||
|
||||
Full-screen, no auth, registered TOP-LEVEL beside `/shopfloor` in
|
||||
`frontend/src/router/index.js` (outside AppLayout, `meta.plugin` only):
|
||||
Full-screen, no auth, exported as a `toplevel` route in
|
||||
`plugins/printedparts/frontend/routes.js` and aggregated into
|
||||
`frontend/src/router/index.js` via `routes.gen.js` (outside AppLayout,
|
||||
`meta.plugin` only):
|
||||
|
||||
```javascript
|
||||
{
|
||||
@@ -961,7 +964,7 @@ Commit + tag `lab-stage-07`.
|
||||
|
||||
A plugin OWNS its label page (the USB precedent) - parts are not assets, so
|
||||
they do not join the shared asset-label TYPE_CONFIG. New public route beside
|
||||
`/print/usb-labels`, view `frontend/src/views/print/PrintedPartsLabels.vue`.
|
||||
`/print/usb-labels`, view `plugins/printedparts/frontend/views/PrintedPartsLabels.vue`.
|
||||
The pieces that matter:
|
||||
|
||||
```javascript
|
||||
@@ -1184,7 +1187,7 @@ for a contributor:
|
||||
| Blueprint style, pagination, authz | `plugins/measuringtools/api/routes.py` |
|
||||
| Image upload/serve/delete | `shopdb/core/api/models.py` |
|
||||
| Open kiosk endpoints precedent | `plugins/employees/api/routes.py`, `plugins/notifications/api/routes.py` |
|
||||
| Plugin-owned label print view | `frontend/src/views/print/USBLabelBatch.vue` |
|
||||
| Plugin-owned label print view | `plugins/usb/frontend/views/USBLabelBatch.vue` |
|
||||
| Barcode/QR rendering | JsBarcode in `AssetLabel.vue`, `qrLogo.js` |
|
||||
| Kiosk route posture | `/shopfloor` in `frontend/src/router/index.js` |
|
||||
| List/Detail master templates | `PrintersList.vue`, `PrinterDetail.vue` |
|
||||
|
||||
@@ -91,13 +91,14 @@ audit_log(action='created', entitytype='Camera', entityid=asset.assetid, entityn
|
||||
|
||||
## Step 4: Install the plugin
|
||||
|
||||
First add `plugins/cameras/migrations/` with a per-plugin Alembic chain that creates the plugin's tables, and register those tables in `PLUGIN_TABLE_OWNERS` (per ADR-008; the plugin chain owns plugin schema, never the core chain). Then:
|
||||
|
||||
```bash
|
||||
flask plugin install cameras
|
||||
flask db migrate -m "Add cameras plugin tables"
|
||||
flask db upgrade
|
||||
flask plugin upgrade-all
|
||||
```
|
||||
|
||||
`install` runs the plugin's `on_install` hook (which seeds the AssetType row), registers it in the plugin registry, and runs migrations.
|
||||
`install` runs the plugin's `on_install` hook (which seeds the AssetType row), registers it in the plugin registry, and runs the plugin's own migration chain. `flask db migrate`/`flask db upgrade` is reserved for core tables and must not be used for plugin schema.
|
||||
|
||||
## Step 5: Verify it works
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ These plugins are in `plugins/` in this repo. Enable per site with `flask plugin
|
||||
| `usb` | USB devices issued to shop-floor users | Lightweight checkout / check-in. |
|
||||
| `notifications` | Shop-floor notifications, recognitions, kiosk feed | Used by `ShopfloorDashboard.vue`. |
|
||||
| `measuringtools` | Metrology and inspection instruments: calipers, micrometers, thread/bore/height gages, indicators | Per [ADR-005](adr/ADR-005-equipment-vs-measuringtools.md). Calibration lifecycle with derived status. First plugin built on the matured scaffold; its walkthrough is [PLUGIN-GUIDE.md](PLUGIN-GUIDE.md). Ships `default_enabled: false`. |
|
||||
| `employees` | Read-only employee directory lookup | Backed by a separate HR database. Ships `default_enabled: false`. |
|
||||
| `geenforce` | GE-Enforce manifest store: imaging PC-type scopes and their install manifests (apps, scripts, files, registry, version gates) | Per [ADR-012](adr/ADR-012-geenforce-manifest-ownership.md). Served to the GE-Enforce client as JSON. Requires GE-Enforce lib >= 2.6 on target PCs. Ships `default_enabled: false`. |
|
||||
| `knowledgebase` | Knowledge Base articles linking to external resources | Lightweight article store. |
|
||||
| `printedparts` | 3D-printed parts inventory | Kiosk checkout / check-in. Ships `default_enabled: false`. |
|
||||
| `slides` | Slides for the lobby display and shop-floor screensaver | Upload / reorder / delete per surface. |
|
||||
| `warranty` | Asset warranty tracking | Manual entry now, Dell / Lenovo / HP provider lookups later. Derived coverage status with report buckets. |
|
||||
|
||||
## Building your own
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ The last big milestone before 1.0 is the legacy-ASP data import plus a productio
|
||||
|
||||
### Must-have
|
||||
|
||||
- **Asset model fully wired**. `Asset.mapx, Asset.mapy` columns, `AssetRelationship.inheritsposition`, `AssetRelationship.propagatesthroughid` columns. Models match the locked ADR-001 surface that `resolve_asset_position` already targets.
|
||||
- **Asset model fully wired**. `Asset.mapx, Asset.mapy` columns, `AssetRelationship.inheritsposition` column, and the `relationshiptypepropagations` M:N table (`RelationshipTypePropagation` model; propagation lives on `RelationshipType`, not `AssetRelationship`). Models match the locked ADR-001 surface that `resolve_asset_position` already targets.
|
||||
- **Equipment data migration script** for facilities migrating from legacy ASP shopdb. One-shot script under `scripts/migration/`. Per ADR-001, only `category='Equipment' AND machinenumber IS NOT NULL` migrates.
|
||||
- **Printers retirement**. Legacy `PrinterData` model, `printers_bp` legacy blueprint, and the frontend `PrinterForm.vue` references to `printer.printerdata.*` get removed in lockstep. Coordinated with the equipment migration.
|
||||
- **Printers retirement**. The printers plugin already runs on the asset architecture (blueprint `printers_asset_bp`); any remaining legacy printer-table cleanup is coordinated with the equipment migration.
|
||||
- **External plugin UI packaging**. The Vue-side hook contract ships (ADR-010: get_settings_cards / get_asset_panels / get_map_overlays / get_asset_presentation) and route gating is backend-driven (ADR-009), but plugin routes/views still live in core `frontend/src`. Let an external plugin ship its own Vue bundle so adopters can add UI without editing core.
|
||||
|
||||
### Nice-to-have
|
||||
|
||||
@@ -105,7 +105,10 @@ default need to act.
|
||||
To check what your instance points at:
|
||||
|
||||
```bash
|
||||
docker compose exec api flask shell -c "from shopdb.core.models.setting import Setting; print(Setting.query.filter(Setting.key.like('map_blueprint%')).all())"
|
||||
docker compose exec -T api flask shell <<'PY'
|
||||
from shopdb.core.models.setting import Setting
|
||||
print(Setting.query.filter(Setting.key.like('map_blueprint%')).all())
|
||||
PY
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
@@ -183,7 +183,7 @@ Skipped from migration:
|
||||
## References
|
||||
|
||||
- `shopdb/core/models/asset.py`
|
||||
- `shopdb/core/models/machine.py` (legacy, deprecated)
|
||||
- (core `Machine` model retired per this decision; machine data now owned by the machines plugin at `plugins/machines/models/machine.py`)
|
||||
- `shopdb/plugins/base.py`
|
||||
- ADR-002 (versioning of the surface)
|
||||
- ADR-003 (plugin distribution)
|
||||
|
||||
@@ -63,16 +63,21 @@ The framework provides:
|
||||
|
||||
## Migration strategy (resolved)
|
||||
|
||||
Deploys run a single core Alembic chain: `flask db upgrade`. Bundled plugins do
|
||||
NOT carry their own migration chains - their tables are folded into the core
|
||||
chain (migration `7c04_fold_plugin_schema`). This was a deliberate resolution of
|
||||
the Phase 7B footgun where bundled-plugin baselines and the core baseline both
|
||||
created the same tables, so `flask plugin upgrade-all` would conflict. A fresh
|
||||
`flask db upgrade` reproduces the live schema exactly (verified on a scratch DB).
|
||||
Deploys run two commands: `flask db upgrade` then `flask plugin upgrade-all`
|
||||
(lean/ADR-014 sites add an optional `flask plugin prune-schema` at initial
|
||||
provisioning). The core Alembic chain applied by `flask db upgrade` creates the
|
||||
full core AND bundled-plugin schema through the chain head (this includes
|
||||
migration `7c04_fold_plugin_schema`). But every bundled plugin still carries its
|
||||
own Alembic chain per ADR-008: `flask plugin upgrade-all` stamps each plugin's
|
||||
own chain (the `alembic_version_<plugin>` tables) and applies any plugin-specific
|
||||
migrations added after the ownership cutover. The earlier Phase 7B state that
|
||||
folded everything into core with no per-plugin chains was superseded by ADR-008's
|
||||
per-plugin ownership. A fresh `flask db upgrade` reproduces the live core schema
|
||||
exactly (verified on a scratch DB).
|
||||
|
||||
External (out-of-tree) plugins per ADR-003 may still ship their own migrations;
|
||||
the framework supports per-plugin chains for them. Only the in-tree bundled
|
||||
plugins are consolidated into core.
|
||||
External (out-of-tree) plugins per ADR-003 ship their own migrations too; the
|
||||
framework runs the same per-plugin chain mechanism (ADR-008) for both bundled and
|
||||
external plugins.
|
||||
|
||||
## Open questions
|
||||
|
||||
|
||||
@@ -145,5 +145,5 @@ Reclassification is one-shot, run once, archived. Like the original migration sc
|
||||
|
||||
- ADR-001 (Asset is platform contract)
|
||||
- ADR-002 (versioning of the surface)
|
||||
- `plugins/equipment/` (current placeholder)
|
||||
- `plugins/machines/` (equipment plugin, renamed per ADR-011)
|
||||
- `plugins/computers/` (existing example of plugin pattern)
|
||||
|
||||
@@ -41,7 +41,9 @@ list. This is the whole of what ships now.
|
||||
anonymous callers is safe because `GET /api/dashboard/navigation`
|
||||
already leaks the same enabled/disabled signal, and unauthenticated
|
||||
kiosk routes (`/tv`) need the answer too. It carries no metadata, so it
|
||||
reveals strictly less than the admin-gated `GET /api/plugins`.
|
||||
reveals strictly less than the equally-anonymous but metadata-carrying
|
||||
`GET /api/plugins` (also `jwt_required(optional=True)`; only the
|
||||
`PUT /api/plugins/<name>` toggle is admin-gated).
|
||||
|
||||
2. **Route tagging.** Every plugin-owned route carries `meta.plugin =
|
||||
'<pluginname>'`. This covers the per-plugin route modules
|
||||
|
||||
@@ -43,7 +43,7 @@ one would mean forking a core view:
|
||||
printer detail page, a calibration-status card on a measuring tool. Today
|
||||
`WarrantyPanel.vue` is composed in by hand-editing each detail view.
|
||||
4. **Map marker / overlay contributions.** A calibration-due badge on the
|
||||
shop-floor map (`frontend/src/views/map/MapView.vue`). The map draws type
|
||||
shop-floor map (`frontend/src/views/MapView.vue`). The map draws type
|
||||
colors but has no plugin decoration path.
|
||||
5. **Search-result rendering / routing for plugin asset types.** Global search
|
||||
returns assets, but core hardcodes how each type renders and where its detail
|
||||
|
||||
@@ -134,7 +134,7 @@ before signing (section 4).
|
||||
increasing `serial` plus a `revoked` list of name-version pairs. Each site
|
||||
records the last-seen serial in instance state and refuses an index with a
|
||||
lower serial (anti-rollback of the catalog itself). The index also carries
|
||||
per-entry version/tier/core_version so `flask plugin shelf list` can display
|
||||
per-entry version/tier/core_version so `flask plugin shelf-list` can display
|
||||
compatibility without unpacking, but the index is a BROWSE layer only:
|
||||
adopt reads dependencies, tier, and core_version from the signed manifest
|
||||
inside the verified artifact, never from the index.
|
||||
@@ -344,7 +344,7 @@ against the real code, not the idealized layout:
|
||||
window.
|
||||
- Version skew across ADR-004 sites: one shelf serves sites at different
|
||||
contract versions. Adopt checks core_version from the signed manifest against
|
||||
the site's own __contract_version__ (authoritative); shelf list shows an
|
||||
the site's own __contract_version__ (authoritative); shelf-list shows an
|
||||
advisory compatibility column from the index. Incompatible artifacts are
|
||||
listable but not adoptable.
|
||||
- Partial/placeholder sync files: fail closed on hash verification; the error
|
||||
@@ -361,7 +361,7 @@ against the real code, not the idealized layout:
|
||||
|
||||
- Phase 0, groundwork (small, days): upgrade_all_plugins uses
|
||||
registry.get_all(); reverse-dep checks read installed manifests from disk;
|
||||
cycle detection in _sort_by_dependencies; docs/plugin-manifest.schema.json +
|
||||
cycle detection in _sort_by_dependencies; shopdb/plugins/manifest_schema.json +
|
||||
`flask plugin validate` (directory mode); `flask plugin apply-profile` with
|
||||
install+enable closure ordering; fix Dockerfile stale comment. All additive,
|
||||
zero risk to running sites.
|
||||
@@ -370,7 +370,7 @@ against the real code, not the idealized layout:
|
||||
ed25519 signing tooling and curator docs. No runtime behavior change yet.
|
||||
- Phase 2, shelf and enforcement (medium-large, one to two weeks):
|
||||
PLUGIN_SHELF_DIR, signed shelf-index with serial + revoked list,
|
||||
`flask plugin shelf list` / `adopt` / `audit` with atomic verified unpack;
|
||||
`flask plugin shelf-list` / `adopt` / `audit` with atomic verified unpack;
|
||||
verify-at-load in load_plugin and verify-at-migrate in
|
||||
run_plugin_migrations, fail-closed in prod; PLUGIN_DEV_TRUST_DIRS for
|
||||
dev/test and the external-repo harness; tier:core lifecycle guard;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Proposal: GE-Enforce as a shopdb plugin
|
||||
|
||||
Status: DRAFT / planning only. Not accepted, not built.
|
||||
Status: ACCEPTED / built - see ADR-012 and plugins/geenforce/.
|
||||
Author: planning session 2026-07-12.
|
||||
|
||||
## 1. What this is
|
||||
|
||||
@@ -174,7 +174,7 @@ Labels (own print view, USBLabelBatch precedent):
|
||||
## 9. Manifest
|
||||
|
||||
name printedparts, version 0.1.0, api_prefix /api/printedparts,
|
||||
core_version ">=0.11.0,<1.0.0", dependencies ["employees"],
|
||||
core_version ">=0.12.0,<1.0.0", dependencies ["employees"],
|
||||
default_enabled false (site opts in - USB precedent).
|
||||
|
||||
## 10. Explicitly out of scope (v1)
|
||||
|
||||
Reference in New Issue
Block a user