# GE-Enforce client integration (shopdb manifest source + reporting) This is the client-side contract for the GE-Enforce manifest-store plugin: how a PC sources its install manifest from shopdb instead of a share file, and how it reports its enforcement result back. It pairs with the plugin proposal in `docs/proposals/ge-enforce-plugin.md`. The reference kit lives in `plugins/geenforce/client/`: - `ShopdbEnforceClient.psm1` - fetch (with ETag + last-known-good cache), shadow compare, and report helpers. - `Invoke-ShopdbEnforce.ps1` - a reference orchestrator that fetches a manifest, runs the UNCHANGED engine against it, and reports the result. These are site-neutral references, not the live dispatcher. A site adapts them into its GE-Enforce.ps1 flow. The engine (`Install-FromManifest.ps1`), detection, self-heal, and SMB payload resolution are untouched - only the source of the manifest JSON moves, plus a result report. ## What does NOT change - The engine and its four filters, all detection methods, self-heal, marker files, and SMB payload staging. - Payload transport for `smb` rows: the client still mounts the share and resolves `apps/...` paths exactly as today. Only the manifest JSON source moves. - The fail-safe posture: any error exits 0. A PC is never blocked or broken because shopdb is unreachable. ## Configuration Registry (provisioned by Azure DSC, same channel as the SFLD credentials): ``` HKLM:\SOFTWARE\GE\ShopDB BaseUrl https://shopdb..geaerospace.net ApiToken ``` Mint the token in shopdb: Settings > API Tokens, scopes `geenforce.fetch` and `geenforce.report`. It is a service token (owner must hold those permissions). ## Fetch contract ``` GET /api/geenforce/manifest?pctype=[&phase=runtime] X-API-Key: If-None-Match: (optional) ``` - `200` - body is the full published manifest JSON for the scope (fat client: the engine filters locally, exactly as today). Response headers carry `ETag` and `X-Manifest-Version`. Cache the body + ETag + version. - `304` - your cached copy is current; use it. - `404` - no such scope, or the scope has no published version yet. - Network failure - enforce from the last-known-good cached manifest (the kit does this automatically) and log a warning. The served manifest is always the current PUBLISHED snapshot, never a live draft being edited in shopdb, so a half-finished edit can never reach a PC. ## Report contract Each enforcement cycle, POST the result (best-effort; a failed report never fails the cycle): ``` POST /api/geenforce/report X-API-Key: Content-Type: application/json { "hostname": "WJCMM01", "scopename": "gea-shopfloor-cmm", "appliedversion": 3, // the published version you actually ran "enforcerversion": "2.6", "counts": { "installed": 1, "skipped": 3, "failed": 0, "filtered": 2 }, "results": [ { "name": "PC-DMIS 2019 R2", "action": "installed", "selfhealed": true }, { "name": "Protect Viewer", "action": "skipped" }, { "name": "eDNC", "action": "failed", "exitcode": 1603, "message": "MSI 1603" } ] } ``` - `appliedversion` lets shopdb show which PCs received the latest manifest (`receivedlatest` in the fleet view). - `action` per entry: `installed` (fired - a self-heal when it should already be 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. 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 `results` list at the call site (`New-ShopdbReport` in the kit takes a summary with `Installed/Skipped/Failed/Filtered` + a `Results` list). ## Cutover (safe, staged) 1. **Configure** the registry values on a canary PC; mint the token. 2. **Shadow mode**: run `Invoke-ShopdbEnforce.ps1 -ShadowMode -ShareManifestPath `. It installs from the SHARE (no behavior change), fetches the shopdb manifest, logs any diff, and reports. Watch for zero diffs across one PC of every pctype for ~20 cycles. 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 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. Do not cut a fleet over before the shadow diffs are clean. Preinstall (`phase=preinstall`) stays share-sourced until its own cutover is planned - it runs before enrollment provisions a token. ## Security notes - The client runs as SYSTEM, so shopdb's TLS certificate must be in the machine trust store (air-gapped/self-signed sites provision the CA via the same DSC step as the token). - `http`/`inline` payloads are verified against `payloadsha256` before running, independent of how the entry detects install state. This is the real integrity guarantee and holds even over plain HTTP inside a trusted segment. - The token is a scoped service token: it can fetch manifests and report, and nothing else.