Per decision: displays need none of the fleet-wide common scope's software, so the gea-shopfloor-display scope carries everything it enforces and does not inherit common. This avoids repackaging common's SMB-backed payloads for a share-less display. - Invert the client common-merge switch: -NoCommon (default-on) becomes -IncludeCommon (default OFF). A scope now enforces alone unless opted in. The capability stays for a future share-less non-display PC; displays omit it. - Drop the common SMB-payload audit + inheritance sections from the display seed comments and docs (GE-ENFORCE-DISPLAY.md); document self-sufficiency. - GE-ENFORCE-CLIENT.md: common-scope inheritance is now opt-in.
8.1 KiB
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
smbrows: the client still mounts the share and resolvesapps/...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.<site>.geaerospace.net
ApiToken <a geenforce.fetch (+ geenforce.report) managed service token>
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=<scope>[&phase=runtime]
X-API-Key: <token>
If-None-Match: <cached ETag> (optional)
200- body is the full published manifest JSON for the scope (fat client: the engine filters locally, exactly as today). Response headers carryETagandX-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: <token>
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" }
]
}
appliedversionlets shopdb show which PCs received the latest manifest (receivedlatestin the fleet view).actionper entry:installed(fired - a self-heal when it should already be present),skipped(detected present),failed,filtered.selfhealedmarks a drift correction.- shopdb keeps the latest report per (hostname, scope, phase) plus history, and 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
results list at the call site (New-ShopdbReport in the kit takes a summary
with Installed/Skipped/Failed/Filtered + a Results list).
The engine emits per-entry outcomes in PascalCase (Name/Action/SelfHealed/ ExitCode/Message); New-ShopdbReport maps every per-entry key down to the
lowercase names above (name/action/selfhealed/exitcode/message) before POST,
so the entire wire contract shopdb reads is lowercase. ConvertTo-ShopdbSummary
first normalizes whatever the engine returns (a well-formed summary, a bare
return code, $null, or several emitted objects) into the count/results shape
New-ShopdbReport expects, so a not-yet-compliant engine still produces a valid
report.
Common-scope inheritance (opt-in, OFF by default)
By default a PC enforces its -Scope ALONE. Pass -IncludeCommon to also fetch
the fleet-wide common scope and merge it on top, mirroring the real
GE-Enforce.ps1 (which applies common\manifest.json first, then the pctype's).
When enabled, Invoke-ShopdbEnforce.ps1 fetches common in addition to
-Scope and merges it via Merge-ShopdbManifests:
- entries are keyed by
Name(case-insensitive); - common's unique entries come first, then all pctype entries (common enforces ahead of the pctype, as on the share);
- on a
Nameconflict the pctype entry wins (its override replaces common's).
Common is fetched over the same fail-safe path (ETag + last-known-good cache).
-CommonScope <name> inherits a different fleet scope; a run whose -Scope
already is the common scope does not merge itself.
Displays do NOT use this: the gea-shopfloor-display scope is self-sufficient,
so the display scheduled task omits -IncludeCommon. Common-merge exists for a
future share-less non-display PC that genuinely needs the fleet-wide entries
(which would first require repackaging common's SMB payloads as http/inline).
The three display subtypes (Dashboard, Lobby, 3D Print Room), selected by
C:\Enrollment\display-type.txt, carry their shared policy inside the display
scope itself, not via common.
Fail-safe is observable, not silent
Any error still exits 0 - a bad web app never blocks or breaks a PC. But a fresh display with an EMPTY cache (first boot, shopdb unreachable or the token rejected with 401 / a TLS-trust failure) would otherwise enforce nothing silently. When no manifest and no cache are available, the kit:
- writes a Windows Application event-log entry (source
ShopdbEnforce, event id 1001, type Error) naming the scope and the reason (HTTP status or transport error), and - fires a best-effort report ping (counts
failed: 1, a single(manifest-fetch)result carrying the reason) so the miss surfaces under GE-Enforce > Enforcement Reports.
The cycle still exits 0; the signal just makes the no-enforcement state visible.
Cutover (safe, staged)
- Configure the registry values on a canary PC; mint the token.
- Shadow mode: run
Invoke-ShopdbEnforce.ps1 -ShadowMode -ShareManifestPath <current share manifest>. 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. - 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 (GE-Enforce > Manifests > Export to Share) so the share stays a break-glass copy. - Payload migration (optional, later): move small scripts/configs to
http/inlinepayloads, verified bypayloadsha256. 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/inlinepayloads are verified againstpayloadsha256before 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.