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>
143 lines
6.0 KiB
Python
143 lines
6.0 KiB
Python
"""GE-Enforce P1 parity gate (Gate A): import + export is behaviorally lossless.
|
|
|
|
Runs the DB-free parity harness (parse -> in-memory rows -> render -> compare)
|
|
over a synthetic, site-neutral manifest that exercises every entry type,
|
|
detection method, targeting filter, RegValue typing, InUseCheck, and preinstall
|
|
flag. The synthetic fixture is what CI gates on (no real site data is vendored
|
|
into the framework repo). If the real GE-Enforce reference share is present
|
|
(dev), it is also checked.
|
|
"""
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from plugins.geenforce.parity import run_parity, load_fixtures
|
|
from plugins.geenforce.importer import discover_share, load_manifest_file
|
|
|
|
|
|
# A site-neutral manifest covering the whole schema surface.
|
|
SYNTHETIC = {
|
|
'Version': '2.6',
|
|
'_comment': 'Synthetic parity fixture (not a real site manifest).',
|
|
'Applications': [
|
|
{
|
|
'_comment': 'MSI with Registry detection, version gate, InUseCheck.',
|
|
'Name': 'Sample MSI', 'Type': 'MSI',
|
|
'Installer': 'apps/sample.msi',
|
|
'InstallArgs': '/qn /norestart ALLUSERS=1',
|
|
'_CmmVersion': '2019',
|
|
'DetectionMethod': 'Registry',
|
|
'DetectionPath': 'HKLM:\\SOFTWARE\\Sample',
|
|
'DetectionName': 'DisplayVersion', 'DetectionValue': '1.2.3.4',
|
|
'InUseCheck': {
|
|
'Behavior': 'CloseAndReopen',
|
|
'Processes': [
|
|
{'Name': 'sample', 'ExePath': 'C:\\sample.exe',
|
|
'GracefulCloseTimeoutSec': 15},
|
|
{'Name': 'other'},
|
|
],
|
|
},
|
|
},
|
|
{
|
|
'Name': 'Sample EXE', 'Type': 'EXE', 'Installer': 'apps/sample.exe',
|
|
'InstallArgs': '/quiet', 'WaitTimeoutSec': 60,
|
|
'DetectionMethod': 'FileVersion',
|
|
'DetectionPath': 'C:\\sample.exe', 'DetectionValue': '1.0.0.0',
|
|
},
|
|
{
|
|
'Name': 'Sample PS1', 'Type': 'PS1', 'Script': 'scripts/run.ps1',
|
|
'Args': '-Force', 'DetectionMethod': 'Always',
|
|
},
|
|
{
|
|
'Name': 'Sample File', 'Type': 'File', 'Source': 'configs/app.json',
|
|
'Destination': 'C:\\ProgramData\\app.json',
|
|
'DetectionMethod': 'Hash', 'DetectionPath': 'C:\\ProgramData\\app.json',
|
|
'DetectionValue': 'a' * 64,
|
|
'PCTypes': ['gea-shopfloor-collections', 'gea-shopfloor-cmm'],
|
|
},
|
|
{
|
|
'Name': 'Sample Registry DWord', 'Type': 'Registry',
|
|
'RegPath': 'HKLM:\\SOFTWARE\\Sample', 'RegName': 'Enabled',
|
|
'RegValue': 1, 'RegType': 'DWord',
|
|
'DetectionMethod': 'ValueMatches',
|
|
'DetectionPath': 'HKLM:\\SOFTWARE\\Sample', 'DetectionName': 'Enabled',
|
|
},
|
|
{
|
|
'Name': 'Sample Registry String', 'Type': 'Registry',
|
|
'RegPath': 'HKLM:\\SOFTWARE\\Sample', 'RegName': 'Mode',
|
|
'RegValue': 'on', 'RegType': 'String',
|
|
},
|
|
{
|
|
'Name': 'Sample INF', 'Type': 'INF', 'Installer': 'configs/driver.inf',
|
|
'DetectionMethod': 'pnputil', 'DetectionPattern': 'SampleDriver',
|
|
},
|
|
{
|
|
'Name': 'Sample bay-gated', 'Type': 'CMD', 'Installer': 'scripts/bay.cmd',
|
|
'TargetMachineNumbers': ['3201', '3202'],
|
|
},
|
|
{
|
|
'Name': 'Sample host-gated', 'Type': 'BAT', 'Installer': 'scripts/host.bat',
|
|
'TargetHostnames': ['WJS-*', 'WJPC0615'],
|
|
},
|
|
{
|
|
'Name': 'Sample always-installs (no detection)', 'Type': 'PS1',
|
|
'Script': 'scripts/every.ps1',
|
|
},
|
|
{
|
|
'Name': 'Sample preinstall-flagged', 'Type': 'EXE',
|
|
'Installer': 'apps/pre.exe', 'PreEnrollment': True,
|
|
'PCTypesStrict': True, 'KillAfterDetection': True,
|
|
'PCTypes': ['gea-shopfloor-nocollections'],
|
|
},
|
|
],
|
|
}
|
|
|
|
REFERENCE_SHARE = '/home/camp/pxe-images/tsgwp00525-v2/shared/dt/shopfloor'
|
|
REFERENCE_PREINSTALL = '/home/camp/projects/pxe/playbook/preinstall/preinstall.json'
|
|
|
|
|
|
def test_synthetic_manifest_round_trips_losslessly():
|
|
"""The synthetic manifest imports + exports with full behavioral parity."""
|
|
results, ok = run_parity([('synthetic', 'runtime', SYNTHETIC)])
|
|
result = results[0]
|
|
assert result['entries_identical'] == result['entries_total'], result['firstdiff']
|
|
assert result['profiles_same'] == result['profiles_total'], result['firstdiff']
|
|
assert ok
|
|
|
|
|
|
def test_regvalue_numeric_type_preserved():
|
|
"""A DWord RegValue of 1 round-trips as int 1, not the string '1'."""
|
|
from plugins.geenforce.importer import build_scope
|
|
from plugins.geenforce.serializer import scope_to_manifest
|
|
|
|
rebuilt = scope_to_manifest(build_scope('synthetic', 'runtime', SYNTHETIC))
|
|
dword = next(e for e in rebuilt['Applications']
|
|
if e['Name'] == 'Sample Registry DWord')
|
|
assert dword['RegValue'] == 1
|
|
assert isinstance(dword['RegValue'], int)
|
|
|
|
|
|
@pytest.mark.skipif(not os.path.isdir(REFERENCE_SHARE),
|
|
reason='GE-Enforce reference share not present')
|
|
def test_reference_share_round_trips_losslessly():
|
|
"""Every real on-share manifest round-trips (the live Gate A)."""
|
|
manifests = list(discover_share(REFERENCE_SHARE))
|
|
if os.path.isfile(REFERENCE_PREINSTALL):
|
|
manifests.append(('preinstall', 'preinstall',
|
|
load_manifest_file(REFERENCE_PREINSTALL)))
|
|
results, ok = run_parity(manifests)
|
|
failed = [r for r in results if not r['passed']]
|
|
assert ok, f"parity failures: {[(r['scopename'], r['firstdiff']) for r in failed]}"
|
|
|
|
|
|
def test_fixtures_cover_every_pctype():
|
|
"""The machine-profile fixtures include one of each imaging pctype."""
|
|
labels = {f['pctype'] for f in load_fixtures()}
|
|
for pctype in ('gea-shopfloor-collections', 'gea-shopfloor-nocollections',
|
|
'gea-shopfloor-common', 'gea-shopfloor-cmm',
|
|
'gea-shopfloor-genspect', 'gea-shopfloor-heattreat',
|
|
'gea-shopfloor-waxtrace', 'gea-shopfloor-partmarker',
|
|
'gea-shopfloor-display'):
|
|
assert pctype in labels
|