# GE-Enforce: the gea-shopfloor-display scope Displays are the share-less corner of the fleet. They are Entra-joined, credential-less kiosk PCs that pull their manifest over HTTPS on port 443 and authenticate with a read-only service PAT scoped `geenforce.fetch`, sent as `X-API-Key`. They have no SMB share mount. The kiosk engine and the kiosk browser are baked into the display image, not shipped over HTTPS, so the display manifest heals POLICY / CONFIG drift only, never EXEs. It is self-sufficient and does not inherit the fleet-wide `common` scope (see below). ## The display fetch token MUST be resource-bound The same read-only key ships to every display (delivered by DSC, or baked into the image), so it must not be a skeleton key for the whole content store. Mint the display token bound to just this scope, so a leak cannot pull any other scope's manifest or any blob by hash: ``` POST /api/apitokens { "name": "display fetch", "scopes": ["geenforce.fetch"], "resourcescopes": ["gea-shopfloor-display"] } ``` With `resourcescopes` set, `GET /manifest?pctype=` returns 403 and `GET /payload/` returns 404 for any blob the display scope does not reference. `resourcescopes` NULL (unset) = unrestricted, for back-compat with existing service tokens. Rotate by minting a new bound token and revoking the old one (deactivate it server-side); DSC re-delivers, or re-image. There are three display subtypes, selected by `C:\Enrollment\display-type.txt`: `Dashboard`, `Lobby`, and `3DPrintRoom`. ## Authoring the scope The scope is authored programmatically by `plugins/geenforce/seed_display_scope.py`, which builds a manifest dict and hands it to `service.replace_scope_draft` (the same call the `import-share` CLI uses), then attaches the inline dispatcher payload. From a Flask app context: ```python from plugins.geenforce.seed_display_scope import seed_display_scope seed_display_scope(publish=True) # publish=False leaves it as a draft ``` `replace_scope_draft` is an idempotent draft rebuild. `publish=True` additionally freezes an immutable published snapshot (that step is not idempotent: it always creates a new version). ### What the scope contains 1. Four `Registry` drift-heal entries that re-assert the Microsoft Edge kiosk relaunch policies set at imaging by `09-Setup-Display.ps1`. Each writes the value and detects drift with `DetectionMethod = ValueMatches` against the same path/name, so a display that loses a policy self-heals on the next enforce cycle with no keyboard or mouse on site: - `RelaunchNotification = 2` (DWord, Required auto-restart) - `RelaunchNotificationPeriod = 3600000` (DWord, 1 hour) - `RelaunchHeadsUpPeriod = 60000` (DWord, 1 minute) - `RelaunchWindow` (String, JSON, 02:00 start, 120 minute duration) 2. One `PS1` dispatcher, delivered inline over HTTPS. It reads `C:\Enrollment\display-type.txt` and launches the kiosk target for the subtype. The subtype -> route map is a data-driven table (`DISPLAY_TYPE_TARGETS`) at the top of both the seed module and the generated script, so targets are easy to edit. `DetectionMethod = Always` so it re-asserts each cycle, but the script is idempotent (it skips relaunch if a kiosk process is already serving the target URL). ### display-type -> target map | display-type.txt | kiosk route | notes | | --- | --- | --- | | `Dashboard` | `/shopfloor` | core ShopfloorDashboard, standalone full-screen | | `Lobby` | `/tv` | slides plugin TV dashboard (surface `lobby`) | | `3DPrintRoom` | `/parts-kiosk` | **PLACEHOLDER, TODO-confirm** printedparts parts kiosk route; confirm the real 3D-print-room target with the floor team before publishing to production displays | ## Self-sufficient: displays do NOT inherit common The `gea-shopfloor-display` scope carries everything a display enforces. It does NOT inherit the fleet-wide `common` scope. Displays run the enforcer with common-merge off (the client default; common-merge is opt-in via `Invoke-ShopdbEnforce.ps1 -IncludeCommon`), so `common`'s SMB-backed fleet entries (Adobe, Oracle, OpenText, Defect Tracker, EventSaver, printer map, self-update, asset-reporting, ...) never reach a share-less display. This was a deliberate decision: a display needs none of common's software, and inheriting common would have forced repackaging every SMB `common` payload as `http`/`inline` for a share-less box. Keeping the display scope self-sufficient avoids all of that. If a future non-display share-less PC genuinely needs the fleet-wide entries, that is what `-IncludeCommon` plus a per-entry SMB->http payload conversion would be for -- but displays do not use it.