Files
AutopilotMonitor/rules/schema/analyze-rule.schema.json
T
Oliver Kieselbach f607e21c13 agent+rules: Hybrid Join user affinity from the IME user token; IME backlog eras anchored by the bootstrap record
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>
2026-09-04 21:12:56 +02:00

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
}
}
}