Adds a coarse virtual-machine flag to every hardware_spec event so the rule
engine's preconditions feature can filter device-specific rules. Wires the
new flag into ANALYZE-SEC-001 (Secure Boot UEFI CA 2023): the rule no longer
fires on Hyper-V / VMware / VirtualBox / Xen / QEMU / Parallels / KVM
guests, where the firmware DB is host-controlled and the inside-the-VM
UEFICA2023Status registry value is not actionable.
Agent (V1 + V2)
- Centralized VM detection in DeviceInfoProvider.IsVirtualMachine(mfr, model)
alongside the existing GetManufacturer / GetModel helpers (which already
handle Lenovo's Win32_ComputerSystemProduct.Version quirk). Reused, not
duplicated — same source of truth as SessionRegistration / DiagnosticsPackage
/ GatherRulesMode.
- New CollectSystemInfo step in DeviceInfoCollector.HardwareSpec emits
systemManufacturer, systemModel, isVirtualMachine onto the hardware_spec
event. No new WMI query — calls into DeviceInfoProvider.
- Conservative bias: Microsoft Corporation manufacturer is shared between
Hyper-V VMs and physical Surface devices, disambiguated via model
("Virtual Machine" → VM, "Surface Laptop 5" → physical).
Rule
- ANALYZE-SEC-001 gains a precondition (equals isVirtualMachine=false) and
bumps to v1.1.0 so EnsureBuiltInRulesSeededAsync re-saves it for tenants
on next backend cold start.
- Old agents (no isVirtualMachine field on hardware_spec) → fail-closed →
rule is silently skipped on those devices until the agent updates. Safe
default for a security rule.
Tests
- DeviceInfoProviderVmDetectionTests (V1 + V2 mirror, 18 cases each):
Hyper-V / Cloud PC / VMware / VirtualBox / Xen / QEMU / Parallels / KVM
positive + Surface Laptop/Pro/Book + Lenovo / Dell / HP negative + null/empty
edge cases. Pinned at the central DeviceInfoProvider so all consumers benefit.
- 781/781 backend, 525/525 V1 agent, 800/800 V2 agent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an optional `preconditions` array to analyze rules. Preconditions
evaluate BEFORE conditions and are AND-combined; any failing precondition
silently skips the rule (no RuleResult, no UI card, no tenant-visible
"rule fired"). Distinct from `conditions`, which decide whether the rule
fires given that it applies.
Use case: rules that should only run on certain hardware/OS profiles
(e.g. "skip ANALYZE-SEC-001 on virtual machines because UEFI CA 2023
deployment is host-controlled").
Backend
- New `RulePrecondition` model + `Preconditions` list on `AnalyzeRule`.
- JSON schema gains optional top-level `preconditions` referencing a new
`rulePrecondition` definition (no signal/required/correlation noise —
pure boolean gate).
- `RuleEngine.EvaluateRule` runs preconditions first; on failure returns
null. `EvaluatePrecondition` reuses MatchesEventType / GetDataFieldValue
/ MatchesOperator. Operator semantics:
* not_exists → passes when event type absent OR field empty (handy
for "old agent without the new field" path).
* exists / others → fail closed when event type or field missing.
- `TableStorageService` rule round-trip extended with `PreconditionsJson`
on Store + Map (column-mapped, not blob — same pattern as Conditions).
Web (analyze-rules editor)
- `AnalyzeRule` / `RuleForm` types gain `preconditions` array; form
payload round-trips on POST + PUT; ruleToForm + EMPTY_FORM updated.
- `AnalyzeRuleFormFields`: new amber Preconditions panel above Conditions
with eventType/dataField/operator/value/description rows.
- `AnalyzeRuleCard`: read-only chip section above Conditions with
"skipped silently if any fails" hint.
Tests
- `RuleEnginePreconditionTests` (7 cases): empty preconditions = legacy
behavior, single pass/fail, AND-semantics with two preconditions,
not_exists passes when event missing, equals fails closed when event
missing, baseline.
- 781/781 backend, 507/507 V1 agent, 782/782 V2 agent, 208/208 web vitest.
No agent change yet — useful preconditions can already target existing
event fields (os_info.edition, hardware_spec.cpuName, tpm_status.specVersion,
…). Adding `hardware_spec.isVirtualMachine` is a follow-up PR (agent collector).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
V2 had drifted to a renamed bucket schema (installedApps/failedApps/...)
plus nested perApp/byPhase. The nested objects were dead — no Web
component or rule consumed them — and the renamed counters needed a
fallback chain in every reader. Single flat schema is simpler:
- Builder rewritten to emit only the flat V1 keys: installed/failed/
skipped/postponed (final), downloading/installing/pending (live),
errorCount/deviceErrors/userErrors/hasErrors/isAllCompleted/
ignoredCount (aggregate helpers), plus name lists per bucket
(installedNames/failedNames/skippedNames/postponedNames/pendingNames).
perApp + byPhase removed. The includePerAppDetail flag is gone — all
emits ship the same shape (live snapshots stay cheap because the
names lists are short strings, not per-app timing objects).
- ignoredCount needed a separate accessor: it lives on the live
AppPackageStateList, not on the deduped phase-snapshot+live union
used by the termination accessor. New ImeIgnoredCount property on
DefaultComponentFactory + ignoredCountAccessor parameter on
EnrollmentTerminationHandler.ctor (default () => 0 keeps existing
test rigs unaffected).
- ANALYZE-APP-007.dataField back to errorCount (flat), dist regenerated.
- Web hooks (useSessionDerivedData, useProgressDerivedData) drop the
V2 fallback chain — single-schema reads only.
- Tests rewritten for V1 schema: counter validation, device/user
error split, hasErrors/isAllCompleted bool helpers, name-list
population per bucket. EnrollmentTerminationHandlerTests collapsed
the three V2-perApp tests into one V1-counts+split-errors test;
per-app timing detail now lives only in FinalStatusBuilder /
final-status.json, not in the event payload.
Per-transition emit cadence and ImmediateUpload=false on live
snapshots are unchanged from the previous commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ANALYZE-APP-007: dataField errorCount -> failedApps. V1 emitted errorCount
in app_tracking_summary; V2 emits failedApps. Pulling the rule to V2-naming
removes the V1-compat alias debt from the agent.
ANALYZE-SEC-001: drop the data. prefix from dataField. RuleEngine's
GetDataFieldValue does flat dictionary lookup; the data. prefix matched
nothing under V1 or V2. Both V1 and V2 emit uefiCA2023Status /
uefiSecureBootEnabled at the root of Data, so the rule was a long-standing
no-match.
Regenerated rules/dist/analyze-rules.json from the source files via
rules/scripts/combine.js.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The backend already reads errorCode from event data for FailureCode and
failure reason formatting, but the agent never populated it. This change
extracts error codes (exit codes, HRESULT) from named regex capture groups
in IME log error patterns and sends them as structured errorCode fields.
Also fixes a pre-existing bug where ErrorPatternId/ErrorDetail were not
persisted across agent restarts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
.* doesn't match newlines without Singleline flag — script stdout/stderr
from AgentExecutor.log spans multiple lines and was never captured.
Changed to [\s\S]*? to match across newlines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The FilterByVersion wildcard logic was treating CVEs with specific
versions in their CPE URI as affecting ALL versions. Now extracts the
version from the CPE 2.3 URI and compares it against the installed
version, so only truly relevant CVEs are shown.
Also adds missing CPE seed mappings for common Intune/Autopilot
managed software: Intune Management Extension, Company Portal,
ODBC/OLE DB drivers, Remote Desktop, Azure CLI.
https://claude.ai/code/session_01Kv2aAEVqbeFSsyPv7gU7pF
- Galactic Admin Mode for Audit Logs: banner + cross-tenant endpoint (galactic/audit/logs)
- Audit page pagination (12 entries per page) with Previous/Next controls
- Platform Metrics: moved computation to backend PlatformMetricsService with 5-min cache
- New endpoint GET /api/galactic/metrics/platform replaces N+1 client-side fetches
- IME log tracker enhancements, DO telemetry pattern, and download progress improvements
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
rules/dist/ was excluded by the global dist/ .gitignore pattern.
Added !rules/dist/ exception so the combine-rules workflow can
commit the generated files. Also adds the initial dist files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the missing operator to the enum so ANALYZE-APP-006.json passes
schema validation in the combine-rules workflow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## Rules as GitHub single source of truth
- Add 75 individual JSON rule files (2 gather, 18 analyze, 55 IME patterns)
under rules/gather/, rules/analyze/, rules/ime-log-patterns/
- Add JSON Schemas for all three rule types with VS Code IntelliSense support
- Add CI combine script (rules/scripts/combine.js) + GitHub Action that
validates schemas with ajv-cli and generates rules/dist/ combined files
- Convert BuiltIn*.cs from hardcoded C# objects to embedded JSON resource loaders
- Add GitHubRuleRepository service to fetch dist/ files from raw.githubusercontent.com
- Add ReseedFromGitHubFunction: POST /api/rules/reseed-from-github?type=all|gather|analyze|ime
## IME Log Patterns
- Extend ImeLogPattern model with Description and IsBuiltIn properties
- Add ImeLogPatterns table + API constants
- Add TableStorageService CRUD methods for IME log patterns
- Add ImeLogPatternService (CRUD, merge, auto-seed from embedded JSON)
- Add ImeLogPatternsFunction: GET/PUT /api/rules/ime-log-patterns (Phase 1: read-only
for tenants, Galactic Admin can enable/disable/edit globally)
- Add IME Log Patterns management page (/ime-log-patterns)
- Wire GetAgentConfigFunction to use ImeLogPatternService instead of static class
## API route consolidation
- All rule endpoints now under /api/rules/* for single certificate exclusion path:
/api/rules/gather, /api/rules/analyze, /api/rules/ime-log-patterns,
/api/rules/reseed-from-github
## Export buttons
- Add per-rule and export-all JSON download buttons to gather-rules,
analyze-rules, and ime-log-patterns pages
## Admin configuration
- Replace old individual reseed buttons with unified "Fetch & Reseed from GitHub"
(calls new GitHub endpoint) plus compact individual-type reseed buttons
- Add CDN cache hint: GitHub raw.githubusercontent.com caches up to 5 minutes
## Preview mode
- ?preview=1 URL param seeds localStorage.previewMode (persists across navigation)
- ?preview=0 clears it; toggle in Navbar settings dropdown (indigo, eye icon)
- IME Log Patterns nav link gated behind galacticAdminMode OR previewMode
- Preview badge shown on nav link when active for normal users
## Tests
- Add xUnit test project (net8.0) with 5 tests for embedded resource loading,
known IME actions validation, and valid categories check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>