mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-23 20:26:10 +00:00
f607e21c13
hybrid_login_pending now measures the absence of a real-user desktop after a reboot (cancelled by the DesktopArrivalDetector or a real JoinInfo user), is never armed on WhiteGlove devices (resume marker, Part-2 flag, .part1- archive) and reports the JoinInfo placeholder as a fact only — on a Hybrid join the AD sign-in never replaces it. New EntraUserAffinityDetector: armed at the real-user desktop on Hybrid devices, the first IME-TOKEN-SUCCESS line after the desktop posts ime_user_token_acquired; ten minutes without one post entra_user_affinity_pending with the IME token-failure codes. ImeLogTracker exposes raw per-line token callbacks past the historic-replay gate; HelloTracker surfaces User Device Registration 304/305 as device_registration_event. ANALYZE-ID-004 v2.0.0 keys on those events (event_type not_exists gained a branch), GATHER-DEVICE-006 follows, GATHER-DEVICE-007/008 collect the Entra operational log. Constants + MCP catalog gain the five types (D-072), the guard test learns the multi-line OnTraceEvent shape, gather_* outputs pass the MCP filter gate by prefix. CMTrace: ImeLogEraPreScan/CmTraceEraTable anchor the backlog era that holds the bootstrap execution record against the Deployed install marker (0.13-0.36 s in the field), whole era or nothing, sibling files by local time; origin era-anchored. Backend phase_duration/app_install_duration refuse a reader-zone-fallback endpoint. Decisions D-190, D-191. Session a7140f98. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
397 lines
17 KiB
JSON
397 lines
17 KiB
JSON
{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": "https://raw.githubusercontent.com/OliverKieselbach/Autopilot-Monitor/main/rules/schema/analyze-rule.schema.json",
|
|
"title": "Autopilot Monitor Analyze Rule",
|
|
"description": "Defines how to analyze collected events to detect issues during Autopilot enrollment.",
|
|
"oneOf": [
|
|
{ "$ref": "#/$defs/analyzeRule" },
|
|
{
|
|
"type": "object",
|
|
"properties": {
|
|
"$schema": { "type": "string" },
|
|
"rules": {
|
|
"type": "array",
|
|
"items": { "$ref": "#/$defs/analyzeRule" }
|
|
}
|
|
},
|
|
"required": ["rules"],
|
|
"additionalProperties": false
|
|
}
|
|
],
|
|
"$defs": {
|
|
"analyzeRule": {
|
|
"type": "object",
|
|
"properties": {
|
|
"$schema": {
|
|
"type": "string",
|
|
"description": "JSON Schema reference for editor IntelliSense."
|
|
},
|
|
"ruleId": {
|
|
"type": "string",
|
|
"description": "Unique rule identifier (e.g., \"ANALYZE-APP-001\").",
|
|
"pattern": "^ANALYZE-[A-Z]+-\\d{3}$"
|
|
},
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Human-readable rule title (e.g., \"Proxy Authentication Required\")."
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Detailed description of what this rule detects."
|
|
},
|
|
"severity": {
|
|
"type": "string",
|
|
"description": "Severity level of the detected issue.",
|
|
"enum": ["info", "warning", "high", "critical"]
|
|
},
|
|
"category": {
|
|
"type": "string",
|
|
"description": "Rule category.",
|
|
"enum": ["network", "identity", "enrollment", "apps", "esp", "device", "security"]
|
|
},
|
|
"version": {
|
|
"type": "string",
|
|
"description": "Semantic version of this rule (e.g., \"1.0.0\").",
|
|
"default": "1.0.0"
|
|
},
|
|
"author": {
|
|
"type": "string",
|
|
"description": "Author of this rule.",
|
|
"default": "Autopilot Monitor"
|
|
},
|
|
"enabled": {
|
|
"type": "boolean",
|
|
"description": "Whether this rule is enabled. Set to false to add a rule without activating it immediately.",
|
|
"default": true
|
|
},
|
|
"isCommunity": {
|
|
"type": "boolean",
|
|
"description": "Whether this is a community-contributed rule. Community rules are stored in the global partition alongside built-in rules.",
|
|
"default": false
|
|
},
|
|
"trigger": {
|
|
"type": "string",
|
|
"description": "Rule trigger type: \"single\" matches individual events, \"correlation\" combines multiple event types.",
|
|
"enum": ["single", "correlation"],
|
|
"default": "single"
|
|
},
|
|
"evaluateOn": {
|
|
"type": "array",
|
|
"description": "When this rule is evaluated. Absent = [\"enrollment_end\"] (terminal-only, the historical behavior). Interim triggers let the rule fire before the session is terminal: \"whiteglove_sealed\" (first genuine WhiteGlove seal) and \"on_event:<eventType>\" (an ingest batch contained that event type). High-frequency telemetry event types are HARD-BLOCKED as on_event triggers (guardrails blockedInterimTriggerEventTypes): the backend rejects them on save and the runtime ignores them. Interim runs suppress markSessionAsFailed and record no stats; findings notify once per (session, rule). Interim-enabled rules need monotonic conditions — a not_exists precondition on enrollment_complete/enrollment_failed/session_timeout passes trivially mid-run.",
|
|
"items": {
|
|
"type": "string",
|
|
"pattern": "^(enrollment_end|whiteglove_sealed|on_event:[a-z0-9_]{1,128})$"
|
|
},
|
|
"minItems": 1,
|
|
"uniqueItems": true
|
|
},
|
|
"conditions": {
|
|
"type": "array",
|
|
"description": "Conditions evaluated against the event stream. All required conditions must match for the rule to fire.",
|
|
"items": { "$ref": "#/$defs/ruleCondition" },
|
|
"minItems": 1
|
|
},
|
|
"preconditions": {
|
|
"type": "array",
|
|
"description": "Optional device-fact gates evaluated BEFORE conditions. ALL preconditions must pass; if any fails the rule is silently skipped — no result, no UI card. Useful for filtering out hardware/OS profiles where the rule does not apply (e.g. virtual machines).",
|
|
"items": { "$ref": "#/$defs/rulePrecondition" },
|
|
"default": []
|
|
},
|
|
"baseConfidence": {
|
|
"type": "integer",
|
|
"description": "Base confidence score (0-100) when required conditions match.",
|
|
"minimum": 0,
|
|
"maximum": 100,
|
|
"default": 50
|
|
},
|
|
"confidenceFactors": {
|
|
"type": "array",
|
|
"description": "Additional factors that increase confidence when matched.",
|
|
"items": { "$ref": "#/$defs/confidenceFactor" },
|
|
"default": []
|
|
},
|
|
"confidenceThreshold": {
|
|
"type": "integer",
|
|
"description": "Minimum confidence score (0-100) to create a RuleResult.",
|
|
"minimum": 0,
|
|
"maximum": 100,
|
|
"default": 40
|
|
},
|
|
"explanation": {
|
|
"type": "string",
|
|
"description": "Detailed explanation of the detected issue. Supports markdown formatting."
|
|
},
|
|
"remediation": {
|
|
"type": "array",
|
|
"description": "Steps to remediate the detected issue.",
|
|
"items": { "$ref": "#/$defs/remediationStep" },
|
|
"default": []
|
|
},
|
|
"relatedDocs": {
|
|
"type": "array",
|
|
"description": "Links to relevant documentation.",
|
|
"items": { "$ref": "#/$defs/relatedDoc" },
|
|
"default": []
|
|
},
|
|
"tags": {
|
|
"type": "array",
|
|
"description": "Tags for filtering and categorization.",
|
|
"items": { "type": "string" },
|
|
"default": []
|
|
},
|
|
"markSessionAsFailedDefault": {
|
|
"type": "boolean",
|
|
"description": "When true, a firing of this rule causes the entire enrollment session to be marked as failed (KO criterion). Tenants can override this via the portal.",
|
|
"default": false
|
|
},
|
|
"templateVariables": {
|
|
"type": "array",
|
|
"description": "Template variables that must be customized per-tenant. If non-empty, the rule is a template: enabling it creates a tenant custom copy with the user's values substituted.",
|
|
"items": { "$ref": "#/$defs/templateVariable" },
|
|
"default": []
|
|
},
|
|
"derivedFromTemplateRuleId": {
|
|
"type": "string",
|
|
"description": "If this custom rule was created from a template, the original template rule's ID. Used to track lineage and prevent duplicate copies."
|
|
}
|
|
},
|
|
"required": ["ruleId", "title", "severity", "category", "conditions", "explanation"],
|
|
"additionalProperties": false
|
|
},
|
|
"ruleCondition": {
|
|
"type": "object",
|
|
"description": "A condition evaluated against the event stream.",
|
|
"properties": {
|
|
"signal": {
|
|
"type": "string",
|
|
"description": "Descriptive name for this signal (e.g., \"proxy_407_error\")."
|
|
},
|
|
"source": {
|
|
"type": "string",
|
|
"description": "Source of the signal. phase_duration and app_install_duration never derive a duration from an IME-log timestamp whose sourceOffsetOrigin is reader-zone-fallback (writer timezone unknown to the agent): such a condition evaluates false with evidence timestampProvenance=reader-zone-fallback.",
|
|
"enum": ["event_type", "event_data", "event_data_array", "phase_duration", "event_count", "app_install_duration", "event_correlation", "clock_skew"]
|
|
},
|
|
"skewMetric": {
|
|
"type": "string",
|
|
"description": "For clock_skew only: which device-clock metric to evaluate. clock_jump = persistent step in the device's clock frame mid-session; sustained_offset = whole session ran on a clock off by at least the threshold. value is the threshold in seconds; operator is limited to gt/gte on the magnitude. IME-log-derived events are excluded from the measurement.",
|
|
"enum": ["clock_jump", "sustained_offset"]
|
|
},
|
|
"eventType": {
|
|
"type": "string",
|
|
"description": "Event type to match on. For event_correlation: the first event type (Event A)."
|
|
},
|
|
"dataField": {
|
|
"type": "string",
|
|
"description": "Data field to match on. Uses dot notation for nested fields (e.g., \"data.errorCode\"). For event_data_array: the array field to iterate (e.g., \"artifacts\")."
|
|
},
|
|
"itemField": {
|
|
"type": "string",
|
|
"description": "For event_data_array only: the sub-field on each array element to test with operator/value (e.g., \"identity\"). Empty = treat each element as a scalar. The condition matches when ANY element satisfies the operator."
|
|
},
|
|
"operator": {
|
|
"type": "string",
|
|
"description": "Comparison operator.",
|
|
"enum": ["equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "count_gte", "count_per_group_gte", "in", "not_in"]
|
|
},
|
|
"value": {
|
|
"type": "string",
|
|
"description": "Value to compare against."
|
|
},
|
|
"required": {
|
|
"type": "boolean",
|
|
"description": "Whether this condition must match for the rule to fire. If false, it only contributes to confidence scoring.",
|
|
"default": false
|
|
},
|
|
"filterField": {
|
|
"type": "string",
|
|
"description": "Optional same-event value filter for event_count, event_data and event_data_array: only events whose filterField satisfies filterOperator/filterValue are considered. For event_count it applies before counting (both count_gte and count_per_group_gte, e.g. count only performance_snapshot events with memory_used_percent > 90). For event_data/event_data_array the filter and the main dataField/operator test are evaluated on the SAME event instance — the only way to pin two fields of one event together, since separate conditions each scan all events independently (e.g. system_sleep_episode with durationSeconds gte 300 AND backfilled equals false)."
|
|
},
|
|
"filterOperator": {
|
|
"type": "string",
|
|
"description": "Operator for the same-event value filter. Same vocabulary as operator.",
|
|
"enum": ["equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "in", "not_in"]
|
|
},
|
|
"filterValue": {
|
|
"type": "string",
|
|
"description": "Value for the same-event value filter. Boolean values are stringified (\"true\"/\"false\")."
|
|
},
|
|
"correlateEventType": {
|
|
"type": "string",
|
|
"description": "The second event type to correlate with (Event B). Only used when source = \"event_correlation\"."
|
|
},
|
|
"joinField": {
|
|
"type": "string",
|
|
"description": "Data field to join on — must have the same value in both Event A and Event B."
|
|
},
|
|
"timeWindowSeconds": {
|
|
"type": "integer",
|
|
"description": "Maximum time in seconds between Event A and Event B. Null or 0 means no time limit.",
|
|
"minimum": 0
|
|
},
|
|
"suppressByEvent": {
|
|
"type": "object",
|
|
"description": "Optional suppression: if an event of the specified type exists with the same joinField value as the matched event, the match is skipped. Used to prevent rules from firing when a subsequent event resolved the issue (e.g., app_install_completed suppresses app_install_failed for the same appId).",
|
|
"properties": {
|
|
"eventType": {
|
|
"type": "string",
|
|
"description": "The event type that resolves/suppresses the matched event (e.g., \"app_install_completed\")."
|
|
},
|
|
"joinField": {
|
|
"type": "string",
|
|
"description": "The data field to join on — must have the same value in both the matched event and the suppressing event (e.g., \"appId\")."
|
|
}
|
|
},
|
|
"required": ["eventType", "joinField"],
|
|
"additionalProperties": false
|
|
},
|
|
"eventAFilterField": {
|
|
"type": "string",
|
|
"description": "Optional filter field on Event A (the first event)."
|
|
},
|
|
"eventAFilterOperator": {
|
|
"type": "string",
|
|
"description": "Operator for the Event A filter.",
|
|
"enum": ["equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "count_gte", "count_per_group_gte", "in", "not_in"]
|
|
},
|
|
"eventAFilterValue": {
|
|
"type": "string",
|
|
"description": "Value for the Event A filter."
|
|
}
|
|
},
|
|
"required": ["signal", "source"],
|
|
"additionalProperties": false
|
|
},
|
|
"rulePrecondition": {
|
|
"type": "object",
|
|
"description": "A device-fact gate evaluated before conditions. Pure boolean filter — does not contribute to evidence or confidence.",
|
|
"properties": {
|
|
"source": {
|
|
"type": "string",
|
|
"description": "Currently only event_data is supported.",
|
|
"enum": ["event_data"],
|
|
"default": "event_data"
|
|
},
|
|
"eventType": {
|
|
"type": "string",
|
|
"description": "Event type carrying the field to test (e.g., \"hardware_spec\", \"os_info\", \"tpm_status\")."
|
|
},
|
|
"dataField": {
|
|
"type": "string",
|
|
"description": "Data field to test. Uses dot notation for nested fields."
|
|
},
|
|
"operator": {
|
|
"type": "string",
|
|
"description": "Comparison operator. Same vocabulary as conditions.",
|
|
"enum": ["equals", "not_equals", "contains", "not_contains", "regex", "not_regex", "gt", "lt", "gte", "lte", "exists", "not_exists", "in", "not_in"]
|
|
},
|
|
"value": {
|
|
"type": "string",
|
|
"description": "Value to compare against. Boolean values are stringified (\"true\"/\"false\")."
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Human-readable note explaining the intent (e.g., \"skip on virtual machines\")."
|
|
}
|
|
},
|
|
"required": ["source", "operator"],
|
|
"additionalProperties": false
|
|
},
|
|
"confidenceFactor": {
|
|
"type": "object",
|
|
"description": "A factor that increases confidence when matched.",
|
|
"properties": {
|
|
"signal": {
|
|
"type": "string",
|
|
"description": "Descriptive name for this factor."
|
|
},
|
|
"condition": {
|
|
"type": "string",
|
|
"description": "Condition expression (e.g., \"count >= 5\", \"exists\", \"duration > 300\")."
|
|
},
|
|
"weight": {
|
|
"type": "integer",
|
|
"description": "Confidence weight to add when this factor matches (0-100).",
|
|
"minimum": 0,
|
|
"maximum": 100
|
|
}
|
|
},
|
|
"required": ["signal", "condition", "weight"],
|
|
"additionalProperties": false
|
|
},
|
|
"remediationStep": {
|
|
"type": "object",
|
|
"description": "A remediation step with title and sub-steps.",
|
|
"properties": {
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Title of the remediation approach."
|
|
},
|
|
"steps": {
|
|
"type": "array",
|
|
"description": "Ordered steps to execute.",
|
|
"items": { "type": "string" }
|
|
}
|
|
},
|
|
"required": ["title", "steps"],
|
|
"additionalProperties": false
|
|
},
|
|
"relatedDoc": {
|
|
"type": "object",
|
|
"description": "A link to related documentation.",
|
|
"properties": {
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Display title for the link."
|
|
},
|
|
"url": {
|
|
"type": "string",
|
|
"description": "URL to the documentation.",
|
|
"format": "uri"
|
|
}
|
|
},
|
|
"required": ["title", "url"],
|
|
"additionalProperties": false
|
|
},
|
|
"templateVariable": {
|
|
"type": "object",
|
|
"description": "A variable in a rule condition that must be customized per-tenant before the rule can be used.",
|
|
"properties": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Machine name for this variable (e.g., \"cert_subject\")."
|
|
},
|
|
"label": {
|
|
"type": "string",
|
|
"description": "Human-readable label shown in the configuration UI."
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Help text explaining what value is expected."
|
|
},
|
|
"conditionIndex": {
|
|
"type": "integer",
|
|
"description": "Zero-based index into the conditions array where this variable lives.",
|
|
"minimum": 0
|
|
},
|
|
"field": {
|
|
"type": "string",
|
|
"description": "Which field on the condition to customize.",
|
|
"enum": ["value", "eventType", "dataField", "eventAFilterValue"],
|
|
"default": "value"
|
|
},
|
|
"placeholder": {
|
|
"type": "string",
|
|
"description": "The placeholder value that ships with the template (e.g., \"CN=YOUR-CERTIFICATE-SUBJECT\")."
|
|
},
|
|
"validation": {
|
|
"type": "string",
|
|
"description": "Optional regex pattern to validate user input.",
|
|
"format": "regex"
|
|
}
|
|
},
|
|
"required": ["name", "label", "conditionIndex", "field", "placeholder"],
|
|
"additionalProperties": false
|
|
}
|
|
}
|
|
}
|