The imaging-PC-type manifest editor, contributed as an ADR-010 settings card
(Integrations group) and an ADR-009 plugin-gated route
(/settings/imagingpctypes, hidden when geenforce is disabled).
- Scope list: every imaging PC type with phase, entry count, and published
version (or "unpublished"). New PC Type button.
- Scope detail: ComputerType/MeasuringToolType mapping + description; Publish,
Versions (with per-version Roll Back), Preview (draft JSON), Delete.
- Entry table: ordered with Move Up/Down (the ordering contract, not drag),
Name/Type/Detection/Filters, Edit/Delete. Add Entry opens a typed modal whose
fields switch on entry Type (MSI/EXE/... vs PS1 vs File vs Registry), with a
detection block, comma-separated targeting filters, CMM version gate, payload
source, and an Advanced disclosure for the inert ApplyMode/UpdateWindow and
InUseCheck. RegValue is typed by RegType (DWord/QWord -> number).
- Simulator: "what would a PC get" - enter a machine profile, see which entries
apply and which filter excluded the rest. Verified live: CMM version 2019 ->
applies 2019 + untagged, filters 2016/2026 by _CmmVersion.
Uses the P2 admin API; JWT+admin gated. Frontend build + naming green; full
backend suite 876 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full HTTP admin surface behind the manifest editor (geenforce.manage for edits,
geenforce.publish for shipping):
- Scopes: POST/GET/PUT/DELETE /scopes[/<id>] (create imaging PC types, edit the
ComputerType/MeasuringToolType mapping + metadata, delete).
- Entries: POST /scopes/<id>/entries, PUT/DELETE /entries/<id>. Payloads use the
manifest Applications[] shape; populate_entry (refactored out of build_entry)
updates an entry in place, resetting omitted fields and replacing children.
- Reorder: PUT /scopes/<id>/entries/reorder enforces the ordering contract
(body must list exactly the scope's entry ids).
- Simulate: GET /scopes/<id>/simulate?pctype&subtype&hostname&machinenumber&
cmmversion returns which entries apply and which filter excluded the rest,
reusing the engine-mirror filters. The "what would this PC get" tool.
- Publish lifecycle: POST /scopes/<id>/publish (records publishedby from JWT),
GET /scopes/<id>/versions, GET .../versions/<n> (frozen manifest),
POST /scopes/<id>/rollback.
Entry type validated against ENTRY_TYPES; 8 CRUD tests. JWT+permission gated so
the authz sweep covers them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PCs now report enforcement results back to shopdb, closing the desired-vs-observed
loop.
- POST /api/geenforce/report (geenforce.report service token): each cycle a PC
posts the published version it applied, install/skip/fail/filtered counts, and
per-entry outcomes.
- Two tables: manifestenforcementreports (latest-per-host + history: applied
version, enforcer version, counts, derived status ok/selfhealed/failed) and
manifestenforcementresults (per entry: action installed/skipped/failed,
selfhealed flag, exit code, warning/error message).
- RECEIVED: reports carry the applied version; the admin view derives
receivedlatest by comparing it to the scope's current published version, so
the fleet view shows which PCs picked up an update.
- SELF-HEAL: per-entry action captures drift correction (installed when it
should already be present) vs skipped (already good) vs failed, with messages.
- Admin reads: GET /reports (fleet compliance rollup) and GET /reports/<id>
(per-entry detail). New geenforce.report permission.
- Tables added to the (undeployed) 0001 baseline; geenforce.post_report is a
service-token endpoint so it is exempt from the JWT authz sweep, like the
collector blueprint. 8 reporting tests; full suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First execution phases of docs/proposals/ge-enforce-plugin.md. The GE-Enforce
manifest becomes shopdb data.
P0 scaffold: new geenforce plugin (api_prefix /api/geenforce, default_enabled
false, core_version >=0.7.0). Registered in PLUGIN_TABLE_OWNERS (ADR-008); its
0001 baseline really creates the tables.
P1a model: one wide manifestentries table + entrytype discriminator (not STI,
not JSON blob), manifestscopes (UNIQUE scopename+phase), the three multi-value
filter child tables, inusechecks + processes, immutable manifestpublishedversions
(frozen rendered JSON), manifestpayloads (inline, capped), pctypealiases
(mirror of the engine lib's alias graph). regvalue stored as its raw JSON
literal so DWord typing survives.
P1c importer + exporter: parse common + gea-shopfloor-* + preinstall.json into
draft rows and rebuild the JSON verbatim from rows in sortorder.
P1d parity harness (GATE A): filters.py mirrors the engine's four filter
functions + alias graph; parity.py proves import+export is behaviorally lossless
(field-identical + same-entries-fire across 18 machine-profile fixtures) WITHOUT
byte-diffing. Verified PASS against all 11 real reference manifests (64 entries)
and a synthetic site-neutral fixture covering every type/filter (the CI gate).
First slice (gea-shopfloor-cmm shape): service layer (import/publish/rollback/
export-to-share), CLI (parity, import-share, publish, export-share), and the
client endpoint GET /api/geenforce/manifest serving the current published
snapshot (never the draft) with ETag/304. Split permissions
geenforce.manage/publish/fetch. Tests prove import->publish->serve, draft edits
never change served bytes, publish+rollback, and auth (401 unauth/wrong-scope).
Contract 0.11.0: added service_token_authorized(scope) to shopdb.api so plugin
service endpoints authorize a scoped managed token without importing core token
internals. Documented in PLUGIN-HOOKS.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>