From 3355436fcd0b7fb380d3203330ac1647b0fa77b6 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Mon, 13 Jul 2026 06:45:10 -0400 Subject: [PATCH] Add curated manifest-entry -> Application link (honest app tracking) The honest replacement for the backed-out auto-seeding: instead of scraping manifest labels into duplicate Application rows, an entry can be LINKED to an existing catalog Application, cross-referencing what shopdb already tracks. - Model: manifestentries.appid (nullable soft ref to core applications; in the 0001 baseline). It is shopdb METADATA, deliberately NOT a manifest field - it never appears in the rendered manifest JSON, so enforcement + parity are unaffected (test asserts it stays out of the preview manifest). - API: _entry_payload returns appid + resolved appname; create/update accept an optional appid (validated, unknown id ignored, null unlinks) via _apply_app_link; GET /geenforce/applications is the picker source (id + name). - Editor: a "Tracked application (optional)" select in the entry modal, and the entry summary line notes the linked app ("...; tracked: eDNC"). - Foundation for a future desired-vs-observed compliance view. 889 tests green (incl. the link test + parity/migration unaffected); build + naming green. Co-Authored-By: Claude Opus 4.8 --- .../src/views/geenforce/ManifestEditor.vue | 23 ++++++++++- plugins/geenforce/api/routes.py | 40 +++++++++++++++++-- .../versions/0001_geenforce_baseline.py | 1 + plugins/geenforce/models/manifest.py | 5 +++ tests/test_plugins/test_geenforce_crud.py | 36 +++++++++++++++++ 5 files changed, 100 insertions(+), 5 deletions(-) diff --git a/frontend/src/views/geenforce/ManifestEditor.vue b/frontend/src/views/geenforce/ManifestEditor.vue index 1b8c72c..2837d7c 100644 --- a/frontend/src/views/geenforce/ManifestEditor.vue +++ b/frontend/src/views/geenforce/ManifestEditor.vue @@ -277,6 +277,16 @@ Preinstall runs at imaging and supports MSI/EXE only. +
+ +
@@ -566,6 +576,7 @@ const newScope = ref({ scopename: '', phase: 'runtime' }) const showEntry = ref(false) const entryForm = ref({}) +const applications = ref([]) const showVersions = ref(false) const versions = ref([]) @@ -645,7 +656,7 @@ async function deleteScope() { // -- entries -- function blankEntry() { return { Type: 'MSI', DetectionMethod: '', RegType: 'String', - inuseBehavior: '', inuseProcesses: [], + inuseBehavior: '', inuseProcesses: [], appid: null, PreEnrollment: false, KillAfterDetection: false, PCTypesStrict: false } } function openNewEntry() { @@ -682,7 +693,9 @@ function splitList(value) { } function buildEntryPayload() { const form = entryForm.value - const out = { Name: form.Name, Type: form.Type } + // appid is shopdb metadata, always sent (null unlinks); the backend keeps it + // off the manifest JSON. + const out = { Name: form.Name, Type: form.Type, appid: form.appid ?? null } const scalars = ['Installer', 'InstallArgs', 'Script', 'Args', 'Source', 'Destination', 'RegPath', 'RegName', 'RegType', 'DetectionPath', 'DetectionName', 'DetectionValue', 'DetectionPattern', '_CmmVersion', @@ -828,14 +841,20 @@ function describeEntry(entry) { } else if (entry.PCTypes && entry.PCTypes.length) { text += `; ${entry.PCTypes.length} PC type(s)` } + if (entry.appname) text += `; tracked: ${entry.appname}` return text } function formatDate(value) { return value ? new Date(value).toLocaleString() : '' } +async function loadApplications() { + try { applications.value = payload(await api.get('/geenforce/applications')) } catch (e) { /* optional */ } +} + loadScopes() loadConfig() +loadApplications()