Execute WIKI-UPDATE-PLAN.md (14 items): - NEW docs/PRINTER-INSTALLER.md: install-list / pc-default / install-batch contract + public installer map page. - NEW-shape docs/API-REFERENCE.md: index + pointer to the live generated docs (/api/docs Redoc, openapi.json, llms.txt, MCP), replacing a stale full dump. - geenforce cutover + GE-ENFORCE-DISPLAY/CLIENT/DEPLOY: server-first display dispatcher (display-role by FQDN, display-type.txt fallback), dashboarddefaults FQDN keying, legacy kiosk autostart self-heal (Wow6432Node), per-PC-type cutover status. - PLUGINS: printers/slides rows + plugin-permissions note (slides.manage). - IMPORT-API: dashboarddefaults FQDN-first keying. - CONFIG: word-wise search, site_timezone setting. - PILOT-DEPLOY: servers-to-network reclassify step. IMPORT-ADOPTION: fixup note. - CLAUDE.md: test count 1077->1159, HTTPS-cutover state. CHANGELOG: timezone + kiosk-autostart fixes, site_timezone setting.
18 KiB
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:
- Server (authoritative):
GET $KioskBaseUrl/api/dashboarddefaults/display-role?fqdn=<own-fqdn>. This is a PUBLIC endpoint (no token). The server matches the FQDN against thedashboarddefaultstable (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.- Fallback (offline, or unmapped): the local
C:\Enrollment\display-type.txtvalue against theDISPLAY_TYPE_TARGETSmap 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=<fqdn>(public, unauthenticated). A row indashboarddefaultskeyed 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 localdisplay-type.txtmap below. To repurpose a display, edit itsdashboarddefaultsrow; 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
dashboarddefaultsrows were historically keyed by IP. Migration7d31_dashboarddefault_fqdnadded anfqdncolumn; resolution is now FQDN-first with IP as fallback (_resolve_defaultinshopdb/core/api/dashboarddefaults.py). FQDNs are stored lowercase. This survives DHCP churn on kiosk subnets.POST /api/dashboarddefaultsacceptsfqdn,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.pybuilds 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.mdanddocs/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 isgeenforce-api-cutover.mdsection 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
computerassets. 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" --commitThe 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
Servernetworkdevicetype. 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
PrinterInstallerMapis a public (no-login) frontend page: the floor map with printer hotspots atmapx/mapy. The user clicks printers, the page requests an install batch. The PC's default printer is preselected viapc-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=textreturns 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
defaultprinterasset relationship (PC asset -> printer asset). Returns{printerid, windowsname}, or{}when unknown/none.?format=textreturns 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
.exeinstallpath: PowerShell Invoke-WebRequest download (caller's Windows creds, site base URL + IIS/installersfolder) then run/SILENT- no installpath or
.zip: listed as manual installprinteridsis required (comma-separated, non-numeric tokens ignored).Fleet wiring
The
commonscope'sprinter mapmanifest entry (see GE-ENFORCE-DISPLAY.md) lays down the signed installer. Install name preference:windowsname, elsesharename, 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. LegacyPrinterDataretiring 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 theslides.managepermission, 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 itsplugin.pyget_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, elseipaddress|
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:
- Fleet/client contracts (unauthenticated or token) - geenforce fetch/report +
/payload/<sha>(-> 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). - Import API - pointer to IMPORT-API.md, do not duplicate.
- 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, defaultAmerica/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 viafrontend/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 anHKLM ...\CurrentVersion\Runvalue, 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 intoHKLM\SOFTWARE\Wow6432Node\...\Run, invisible to 64-bit tooling - the reason it survived earlier cleanup. The dispatcher (build_dispatcher_scriptinseed_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 atGET /api/docs/openapi.json(OpenAPI 3.1, ~238 paths / ~362 ops, generated byscripts/gen_openapi.pyfromdocs/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 viapxe-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).