The publishability gate caught internal tooling names and developer paths but nothing site-specific, so roughly sixty leaks reached the wiki: the site name in ten documents, real fleet hostnames in the collector and GE-Enforce examples, an internal database name through the whole import guide, imaging-share paths, and a maintainer's username as the Deciders line of every ADR and inside a generated curl example. None of it is a security matter on an air-gapped fleet. It matters because these pages are read by engineers at other plants, and a document that names one site throughout reads as that site's notes rather than a product's documentation - which is exactly what it then gets treated as. Examples now use neutral hostnames, the site is "the reference site" where the distinction carries meaning, and ADRs are decided by "ShopDB maintainers". The gate carries all of these patterns, so the next one fails a build. Two documents leave docs/ because they were never written for an outside reader. PROJECT-REVIEW.md is an internal health memo pinned to a commit from July, whose headline finding (an untracked playbook) has since been fixed - it is history, and git holds it. PILOT-DEPLOY.md is one site's own cutover runbook, complete with a "re-measure before publishing" placeholder; it moves next to the loader it belongs to, in scripts/site_imports/wjf/. ADR-015 is AMENDED rather than rewritten. Its enforcement section still said report-only and its backlog still listed hardcodes that are now cleared, which left the record contradicting itself. The amendment says what changed and why the report-only period ended; the original text stays, because what the decision looked like when it was taken is the part worth keeping. Also corrects llms.txt's response envelope, which had errors at the top level and pagination at meta.total. Both are nested one deeper, so anything written against that description read undefined on every error it tried to handle.
177 lines
8.1 KiB
Markdown
177 lines
8.1 KiB
Markdown
# 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.<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 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: <token>
|
|
Content-Type: application/json
|
|
{
|
|
"hostname": "CMMPC01",
|
|
"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 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 `Name` conflict 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)
|
|
|
|
1. **Configure** the registry values on a canary PC; mint the token.
|
|
2. **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.
|
|
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 (GE-Enforce > Manifests > 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.
|