diff --git a/docs/WIKI-UPDATE-PLAN.md b/docs/WIKI-UPDATE-PLAN.md deleted file mode 100644 index 772f3e6..0000000 --- a/docs/WIKI-UPDATE-PLAN.md +++ /dev/null @@ -1,225 +0,0 @@ -# WIKI UPDATE PLAN - shopdb-flask docs (as of HEAD ecf4ef6, 2026-07-30) - -Priority order: items 1-4 are stale-on-shipped-features (fix first), 5-6 are new pages, 7-10 are minor rows/notes, 11 is structural. - ---- - -## 1. docs/geenforce-api-cutover.md - UPDATE (and COMMIT - it is untracked) - -**Action: update + `git add`.** This is the most valuable in-flight doc and the only dirty file in the repo. Committing it is step zero of this plan. - -**Changes:** - -a) Section 5, subsection "The display-type.txt dispatcher pattern" (line ~338): it predates commit b22701a. The dispatcher is no longer file-first; it is server-first. Rewrite the subsection opening to: - -> ### The display dispatcher: server-resolved role, file fallback -> -> The inline dispatcher payload (built by `plugins/geenforce/seed_display_scope.py`) resolves what the display should show in two steps: -> -> 1. **Server (authoritative):** `GET $KioskBaseUrl/api/dashboarddefaults/display-role?fqdn=`. This is a PUBLIC endpoint (no token). The server matches the FQDN against the `dashboarddefaults` table (IP fallback) and returns `{role, path, businessunitid, businessunit}`. Roles: `dashboard`, `lobby`, `partskiosk`. Changing a display's job is now a server-side edit; no touch on the PC. -> 2. **Fallback (offline, or unmapped):** the local `C:\Enrollment\display-type.txt` value against the `DISPLAY_TYPE_TARGETS` map baked into the script. If neither resolves, the dispatcher logs and configures nothing. -> -> The FQDN is derived from the hostname plus `DisplayFqdnDomain` (registry) or the built-in default domain. `DetectionMethod = Always`, but the script is idempotent: it rewrites the all-users Startup shortcut only when the resolved target changed. - -b) Section 12 (Open items): add a line that the robocopy `prod-patch-geenforce` reconciliation debt now includes this very doc being untracked - remove that line after commit. - -c) Add a "See also" block near the top linking `GE-ENFORCE-DISPLAY.md`, `GE-ENFORCE-CLIENT.md`, `GE-ENFORCE-DEPLOY.md`, and (new) `API-REFERENCE.md`. - ---- - -## 2. docs/GE-ENFORCE-DISPLAY.md - UPDATE - -**Action: update.** One stale claim plus one missing section. - -**Changes:** - -a) The "display-type -> target map" section (lines ~30, ~61-75) presents `display-type.txt` as THE role source. Retitle the section to **"Role resolution: server first, display-type.txt fallback"** and insert before the table: - -> The dispatcher first asks the server: `GET /api/dashboarddefaults/display-role?fqdn=` (public, unauthenticated). A row in `dashboarddefaults` keyed by the display's FQDN (IP fallback) wins and returns the role and frontend path directly. Only when the server is unreachable or has no mapping does the dispatcher fall back to the local `display-type.txt` map below. To repurpose a display, edit its `dashboarddefaults` row; the change takes effect on the next enforce cycle. - -Keep the existing table, but relabel its caption "fallback map (local file)". - -b) Add a new short section **"Dashboard-defaults FQDN keying"** (this is the home for missing-doc item [D]): - -> ### Dashboard-defaults FQDN keying -> -> `dashboarddefaults` rows were historically keyed by IP. Migration `7d31_dashboarddefault_fqdn` added an `fqdn` column; resolution is now FQDN-first with IP as fallback (`_resolve_default` in `shopdb/core/api/dashboarddefaults.py`). FQDNs are stored lowercase. This survives DHCP churn on kiosk subnets. `POST /api/dashboarddefaults` accepts `fqdn`, `ipaddress`, `displayrole` (`dashboard`|`lobby`|`partskiosk`), `displaypath`, `businessunitid`. Two public read endpoints consume it: `/api/dashboarddefaults/display-role` (dispatcher) and `/api/dashboarddefaults/visitor-location` (lobby business-unit lookup). `plugins/geenforce/seed_display_scope.py` builds the client-side lookup (line ~130). - ---- - -## 3. docs/GE-ENFORCE.md - UPDATE - -**Action: update lines 288-291.** The sentence "Until that cutover, the client only REPORTS; the manifest still comes from the share via Export to Share (4.2)" is false for the displays cohort. Replace with: - -> The cutover from share-sourced to shopdb-sourced manifests is per PC type. The **displays/kiosks cohort has cut over**: share-less display PCs fetch their manifest and payloads entirely over HTTPS (see `docs/geenforce-api-cutover.md` and `docs/GE-ENFORCE-DISPLAY.md`). All other fleet PC types (cmm, collections, keyence, genspect, heattreat, partmarker, nocollections, common) still enforce from the SFLD SMB share via Export to Share (4.2) and only REPORT to shopdb. The playbook for moving the next PC type is `geenforce-api-cutover.md` section 11. - ---- - -## 4. docs/PILOT-DEPLOY.md - UPDATE - -**Action: update.** Two changes: - -a) Line 88 asset-count table predates the servers-to-network reclassify [F]. The writer must re-run the counts on the current prodscratch after the reclassify script; do not hand-edit numbers. Replace the row with re-measured values and a footnote: - -> Counts taken AFTER `scripts/reclassify_servers_to_network.py --commit`. Servers imported as computers are re-pointed to network devices, so the computer count drops and network rises by the same amount versus a raw import. - -b) Add a new numbered step to the import/verify flow, immediately after the import stages complete and before the parallel-validation counts: - -> ### Reclassify servers into network devices -> -> The classic DB stored servers as PCs, so the import lands them as `computer` assets. Re-point them in place: -> -> ``` -> DATABASE_URL=... venv/bin/python scripts/reclassify_servers_to_network.py # dry run, prints matches -> DATABASE_URL=... venv/bin/python scripts/reclassify_servers_to_network.py --commit # apply -> # match on an exact computer type instead of the SVR- name prefix: -> ... --type "Server" --commit -> ``` -> -> The assetid does not change: communications, relationships, map position, and audit history carry over. Only the extension row is swapped (computers -> networkdevices) and the asset type flipped; reclassified devices get the `Server` networkdevicetype. Run the dry run, eyeball the list, then commit. Re-running is safe (already-moved assets no longer match). - ---- - -## 5. NEW: docs/PRINTER-INSTALLER.md - CREATE - -**Action: create.** Missing-doc item [B]. This is a consumed client contract (GE-ENFORCE-DISPLAY.md:82 lists "printer map" as a fleet manifest entry) with zero coverage. Model it on COLLECTOR-INTEGRATION.md (contract doc for an external client). Draft: - -> # Printer installer map and install endpoints -> -> Replaces the classic apiprinters.asp / apipcdefaultprinter.asp / installprinter.asp contract. Shopfloor 2.0 PCs cannot run unsigned .bat maps, so a signed installer EXE (and the public web map page) drives installs from three endpoints in the printers plugin (`plugins/printers/api/asset_routes.py`). All three are `@jwt_required(optional=True)`: anonymous fleet clients work, a logged-in browser also works. -> -> ## The public map page -> -> `PrinterInstallerMap` is a public (no-login) frontend page: the floor map with printer hotspots at `mapx`/`mapy`. The user clicks printers, the page requests an install batch. The PC's default printer is preselected via `pc-default`. -> -> ## GET /api/printers/install-list -> -> Flat, unpaginated list of active NETWORK printers (must have a hostname or a non-USB IP; USB-only printers are excluded). Fields per row: `printerid`, `name`, `machinenumber`, `windowsname`, `sharename`, `hostname`, `ipaddress`, `vendorname`, `modelnumber`, `installpath`, `iscsf`, `locationname`, `mapx`, `mapy`. -> `?format=text` returns a pipe-delimited line per printer (fixed field order: printerid|windowsname|vendorname|modelnumber|hostname|ipaddress|mapx|mapy) so the Inno/Pascal installer does a split() instead of parsing JSON. -> -> ## GET /api/printers/pc-default?machine=NNNN -> -> The PC's default printer by machine (asset) number, persisted at PXE enrollment. Resolved via the `defaultprinter` asset relationship (PC asset -> printer asset). Returns `{printerid, windowsname}`, or `{}` when unknown/none. `?format=text` returns one pipe line, or an EMPTY body for none. -> -> ## GET /api/printers/install-batch?printerids=1,2,3 -> -> Returns a self-deleting Windows .bat installing the selected printers, grouped like classic installprinter.asp: -> - HP / Xerox: one universal `PrinterInstaller.exe /PRINTER="a,b,c"` call -> - printers with a `.exe` installpath: PowerShell Invoke-WebRequest download (caller's Windows creds, site base URL + IIS `/installers` folder) then run `/SILENT` -> - no installpath or `.zip`: listed as manual install -> `printerids` is required (comma-separated, non-numeric tokens ignored). -> -> ## Fleet wiring -> -> The `common` scope's `printer map` manifest entry (see GE-ENFORCE-DISPLAY.md) lays down the signed installer. Install name preference: `windowsname`, else `sharename`, else asset name/number. - -Cross-link it from PLUGINS.md printers row (item 7) and GE-ENFORCE-DISPLAY.md:82. - ---- - -## 6. docs/GE-ENFORCE-DEPLOY.md - UPDATE (minor) - -**Action: update.** Add one cross-link paragraph near the ShopdbUrl/ApiToken registry contract section: - -> For PC types that have cut over to HTTPS manifest delivery (currently displays/kiosks), the full server-side setup, auth model (IP allowlist vs ApiToken), and per-PC-type cutover playbook live in `geenforce-api-cutover.md`. This doc covers what gets laid on the PC; that doc covers where the manifest comes from. - ---- - -## 7. docs/PLUGINS.md - UPDATE (two rows) - -**Action: update.** - -Line 13, printers row, new text: - -> | `printers` | Network and shop-floor printers | Public installer map page + fleet install contract (`/api/printers/install-list`, `/pc-default`, `/install-batch`, see PRINTER-INSTALLER.md). Optional Zabbix integration for supply tracking. Legacy `PrinterData` retiring per ADR-001. | - -Line 22, slides row, new text: - -> | `slides` | Slides for the lobby display and shop-floor screensaver | Upload / reorder / delete per surface. Management gated on the `slides.manage` permission, grantable to non-admin curators; display routes are public. | - -Also add a short **"Plugin permissions"** note after the table (first place any plugin permission is documented): - -> Plugins may register their own permissions (e.g. `slides.manage`). Admins implicitly hold them; grant them to specific roles/users to delegate curation without admin. Each plugin's registered permissions appear in its `plugin.py` `get_permissions()`. - ---- - -## 8. docs/IMPORT-API.md - UPDATE (one row) - -**Action: update line 237.** Replace the dashboarddefaults row: - -> | `dashboarddefaults` | `POST /api/dashboarddefaults` | `fqdn` (preferred key, stored lowercase), `ipaddress` (fallback key), `displayrole` (`dashboard`/`lobby`/`partskiosk`), `displaypath`, `businessunitid` (remapped), `description` | `fqdn`, else `ipaddress` | - -Add a one-line note under the table: "Resolution at runtime is FQDN-first with IP fallback (migration `7d31_dashboarddefault_fqdn`); import both when the legacy source has them." - ---- - -## 9. docs/CONFIG.md - UPDATE (one note) - -**Action: update.** In the "search (dynamic)" section (line ~316), append: - -> Search terms are matched word-wise: a multi-word query returns rows containing EVERY word, each word anywhere in the searched fields, in any order ("CSF Roles" matches a row with "CSF" and "Roles" in different columns). Quoting does not force a contiguous phrase. - -No full doc for [E]; this note plus a CHANGELOG line covers it. - ---- - -## 10. docs/IMPORT-ADOPTION.md - UPDATE (optional, one line) - -**Action: update.** In the per-site loader section, add: "Post-import fixups that re-point existing assets (example: `scripts/reclassify_servers_to_network.py`, servers imported as PCs moved to network devices in place) belong in the site loader's verify stage, not in the stable API layer." - ---- - -## 11. NEW: docs/API-REFERENCE.md - CREATE (proposal) - -**Where:** `docs/API-REFERENCE.md`, wiki-bound alongside the rest. - -**Scope:** an index, not a spec. One table per audience, each row = endpoint, auth, one-line purpose, link to the owning contract doc. Sections: - -1. **Fleet/client contracts (unauthenticated or token)** - geenforce fetch/report + `/payload/` (-> GE-ENFORCE-CLIENT.md, geenforce-api-cutover.md), collector (-> COLLECTOR-INTEGRATION.md), printers install trio (-> PRINTER-INSTALLER.md), `dashboarddefaults/display-role` + `visitor-location` (-> GE-ENFORCE-DISPLAY.md). -2. **Import API** - pointer to IMPORT-API.md, do not duplicate. -3. **Core UI API** - one line: JWT-authenticated, versioned by plugin contract (0.15.0), see CONTRACT-STABILITY.md; enumerate only the public/optional-auth endpoints since those are the site-exposure surface a firewall reviewer asks about. - -**Linked from:** docs/adr/README.md sibling index if one exists, PLUGINS.md header, DEPLOY.md security section (public-endpoint inventory is exactly what a deploy reviewer needs), and each contract doc's "See also". Rule to state at top: detailed request/response shapes live in the contract docs; this page only answers "what endpoints exist, who calls them, what auth". - ---- - -## 12. docs/CONFIG.md - UPDATE (site timezone) + note in notifications coverage - -**Action: update.** New `site_timezone` site setting (default `America/New_York`, public), added in `shopdb/core/api/settings.py` (`build_default_settings`, category `site`) and surfaced in `frontend/src/views/settings/SiteSettings.vue` (Localization group, common-zone dropdown). Add to the site-settings section of CONFIG.md: - -> `site_timezone` (site, default `America/New_York`) - IANA timezone for the site. Notification start/end times are entered and displayed in this zone (not the viewer's browser zone), and daily-reset notification expiry (`expirymode=dailytime`) is computed here. Editable in Settings > Site > Localization. Public-readable so kiosks/clients can resolve it. Stored/served UTC; the frontend converts via `frontend/src/utils/datetime.js` (Intl-based, DST-safe). - -Also add a CHANGELOG line: notification times are now timezone-correct (stored UTC, shown in `site_timezone`); fixes the prior offset bug where a 2:34 PM entry displayed as 6:34 PM. - ---- - -## 13. docs/geenforce-api-cutover.md + GE-ENFORCE-DISPLAY.md - UPDATE (legacy kiosk autostart purge) - -**Action: update both.** The display dispatcher now self-heals the legacy kiosk autostarts the OLD Dashboard/Lobby Inno installers left behind. Add a subsection to the dispatcher coverage in `geenforce-api-cutover.md` (near the dispatcher pattern) and a short note in `GE-ENFORCE-DISPLAY.md`: - -> ### Legacy autostart self-heal -> -> The old GE Aerospace Dashboard / Lobby Display Inno installers planted three autostarts each: a Public-Desktop `.lnk`, an all-users Startup `.lnk`, and an `HKLM ...\CurrentVersion\Run` value, all launching Edge at now-dead URLs (`/shopfloor-dashboard/`, `/tv-dashboard/`) which 404 to a white screen. Because those installers were 32-bit, the Run value was WOW64-redirected into `HKLM\SOFTWARE\Wow6432Node\...\Run`, invisible to 64-bit tooling - the reason it survived earlier cleanup. The dispatcher (`build_dispatcher_script` in `seed_display_scope.py`) now sweeps, every enforce cycle: both the native and Wow6432Node registry views, every loaded user hive (HKU), Run + RunOnce + Policies\Explorer\Run, matching by the legacy value names AND by any value pointing at the old URLs; plus every per-user and common Startup folder; then kills any running old-URL Edge. A read-only locator, `pxe-images/github/find-legacy-kiosk-autostart.ps1`, hunts all these locations (and Edge startup-URL policy, scheduled tasks, Assigned Access) when a straggler persists. - -Note that the kiosk shortcut is a direct Edge shortcut (no launcher/VBS) and that the fix ships by re-publishing the code-authored `gea-shopfloor-display` scope (`seed_display_scope(publish=True)`), not an import-share. - ---- - -## 14. docs/API-REFERENCE.md - CREATE (revise item 11 scope: link the LIVE docs) - -**Action: supersedes item 11's "hand-written index only" framing.** The repo now ships generated, hosted API docs, so API-REFERENCE.md is primarily a pointer to those plus the audience/auth index: - -- **Live interactive spec:** `GET /api/docs` (self-hosted Redoc), raw spec at `GET /api/docs/openapi.json` (OpenAPI 3.1, ~238 paths / ~362 ops, generated by `scripts/gen_openapi.py` from `docs/api-inventory.json`). -- **LLM/agent entry point:** `GET /api/docs/llms.txt` (llms.txt convention) and the read-only MCP server (`mcp/shopdb_mcp.py`, `FastMCP.from_openapi`; set up on a work PC via `pxe-images/github/setup-mcp.cmd`). -- Keep item 11's audience tables (fleet/client contracts, import API, core UI API) as the human index, each row linking the owning contract doc. State at top: interactive/full shapes live at `/api/docs`; contract docs hold the deep semantics; this page answers "what exists, who calls it, what auth". - -Cross-link from PLUGINS.md header, DEPLOY.md security section, and `docs/llms.txt` provenance. The `docs/` blueprint lives in `shopdb/core/api/docs.py` (vendored Redoc under `staticdocs/`); mention it is a core blueprint always mounted. - ---- - -## Out of scope for the wiki but flagged to the maintainer - -- CLAUDE.md drift (test count 1077 vs 1159, phantom "lean-build job", 2026-07-13 "Current state" missing the entire HTTPS-cutover arc) is repo-doc, not wiki, but should ride the same commit. -- No retirements: every existing page stays. ADR-005 must NOT be edited for [F]; ADRs are immutable and its "reclassification" is equipment/measuringtools only. The reclassify script is operational, documented in PILOT-DEPLOY.md (item 4). - -Key source files for the writer: `/home/camp/projects/shopdb-flask/plugins/printers/api/asset_routes.py` (install endpoints, lines 311/432/568), `/home/camp/projects/shopdb-flask/shopdb/core/api/dashboarddefaults.py` (display-role, line 72), `/home/camp/projects/shopdb-flask/plugins/geenforce/seed_display_scope.py` (dispatcher, line ~117-151), `/home/camp/projects/shopdb-flask/scripts/reclassify_servers_to_network.py` (usage in module docstring), `/home/camp/projects/shopdb-flask/shopdb/core/api/search.py` (`_word_match`, lines 27-42), `/home/camp/projects/shopdb-flask/plugins/slides/plugin.py` (slides.manage, line 66).