added skill and docs rules update

This commit is contained in:
Oliver Kieselbach
2026-03-17 23:32:03 +01:00
parent f8b4730225
commit e77111b8cd
5 changed files with 252 additions and 6 deletions
+116
View File
@@ -0,0 +1,116 @@
# Session Debug Skill
Du analysierst eine Autopilot-Monitor Session anhand ihrer SessionId. Du holst dir selbststaendig alle noetigen Daten aus Azure Table Storage via REST API und korrelierst sie mit dem Code.
## Input
Der User gibt eine SessionId (GUID) an, optional gefolgt von einer Frage.
Beispiel: `/session-debug abc12345-1234-1234-1234-123456789abc warum ist die Session failed?`
Die gesamte Eingabe wird als `$ARGUMENTS` uebergeben. Die SessionId ist das erste Wort (GUID-Format), alles danach ist die Frage des Users.
## Hilfsskript
Nutze das Helper-Script `.claude/scripts/query-table.sh` fuer alle Table-Abfragen:
```bash
.claude/scripts/query-table.sh <TableName> "<OData-Filter>" [select_fields]
```
Das Script:
- Parst den Connection String aus `$AUTOPILOT_MONITOR_TABLE_CS`
- Baut die REST API URL zusammen
- Gibt JSON zurueck
## Schritt 1: Session-Metadaten holen
Zuerst die Session aus der **Sessions** Table holen um die TenantId zu bekommen:
```bash
.claude/scripts/query-table.sh Sessions "RowKey eq '${SESSION_ID}'"
```
Aus dem Ergebnis:
- **PartitionKey** = TenantId
- **RowKey** = SessionId
- Wichtige Felder: `Status`, `CurrentPhase`, `EnrollmentType`, `StartedAt`, `CompletedAt`, `FailureReason`, `EventCount`, `DurationSeconds`, `AgentVersion`, `IsPreProvisioned`, `IsHybridJoin`, `IsUserDriven`, `OsBuild`, `SerialNumber`, `DeviceName`, `Manufacturer`, `Model`
Fasse die Session-Metadaten kurz zusammen bevor du weiter machst.
## Schritt 2: Events holen
Mit TenantId und SessionId die Events aus der **Events** Table holen:
```bash
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}'"
```
Events-Struktur:
- **PartitionKey**: `{TenantId}_{SessionId}`
- **RowKey**: `{Timestamp:yyyyMMddHHmmssfff}_{Sequence:D10}` (chronologisch sortiert)
- **EventType**: z.B. `phase_transition`, `app_install_started`, `app_install_completed`, `error_detected`, `esp_page_change`, `hello_screen_detected`, `desktop_arrival`, `enrollment_complete`, `enrollment_failed`, `performance_snapshot`, `agent_metrics_snapshot`, `collector_started`, `collector_stopped`, `completion_check`, etc.
- **Severity**: Trace=-1, Debug=0, Info=1, Warning=2, Error=3, Critical=4
- **Source**: Agent, IME, Registry, WMI, Network
- **Phase**: 0=Unknown, 1=DevicePreparation, 2=DeviceSetup, 3=AccountSetup, 4=DeviceESP, 5=UserESP, 6=Complete, 7=PreProvisioning
- **Message**: Menschenlesbarer Text
- **DataJson**: Strukturierte Metadaten als JSON String (oft die wichtigsten Details!)
- **Sequence**: Reihenfolge bei gleichem Timestamp
## Schritt 3: Bei Bedarf weitere Tables abfragen
Nur abfragen wenn fuer die Frage relevant:
### RuleResults (automatische Analyse-Ergebnisse)
```bash
.claude/scripts/query-table.sh RuleResults "PartitionKey eq '${TENANT_ID}_${SESSION_ID}'"
```
### AppInstallSummaries (App-Installationen)
```bash
.claude/scripts/query-table.sh AppInstallSummaries "PartitionKey eq '${TENANT_ID}'" "PartitionKey,RowKey,AppName,Status,DurationSeconds,StartedAt,CompletedAt"
```
Dann filtern: nur Eintraege deren RowKey mit der SessionId beginnt.
### TenantConfiguration
```bash
.claude/scripts/query-table.sh TenantConfiguration "PartitionKey eq '${TENANT_ID}' and RowKey eq 'config'"
```
## Schritt 4: Analyse
Nachdem du die Daten hast:
1. **Chronologie aufbauen**: Events nach Timestamp sortiert durchgehen
2. **Phasen-Uebergaenge**: phase_transition Events zeigen den Ablauf
3. **Fehler identifizieren**: Events mit Severity >= 2 (Warning) hervorheben
4. **DataJson parsen**: Oft stecken die Details in DataJson - immer reinschauen bei relevanten Events
5. **Code-Korrelation**: Wenn der User eine Frage zum Verhalten hat, lies den relevanten Code im Repo und erklaere anhand der Events was passiert ist
6. **Completion-Logik**: Bei Fragen zur Session-Completion, schau in:
- `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker*.cs`
- Die 3 Completion-Pfade: IME pattern, ESP+Hello composite, Desktop+Hello (no-ESP)
## Wichtige Code-Pfade fuer Event-Analyse
- **Event-Ingestion**: `src/Backend/AutopilotMonitor.Functions/Functions/IngestEventsFunction.cs`
- **Session-Updates**: `src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Sessions.cs`
- **Enrollment Tracker**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker*.cs`
- **ESP/Hello Tracker**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/EspAndHelloTracker*.cs`
- **Desktop Arrival**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DesktopArrivalDetector.cs`
- **Collectors**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/`
- **Web Session View**: `src/Web/autopilot-monitor-web/app/sessions/[sessionId]/`
- **Shared Models/Enums**: `src/Shared/AutopilotMonitor.Shared/`
## Ausgabe-Format
1. **Session-Uebersicht**: Status, Typ, Dauer, Geraet (kompakt, tabellarisch)
2. **Event-Timeline**: Wichtigste Events chronologisch mit Timestamp, EventType, Message (nicht alle - nur relevante, keine performance_snapshot/agent_metrics_snapshot)
3. **Auffaelligkeiten**: Errors, Warnings, ungewoehnliche Muster
4. **Antwort auf User-Frage**: Falls der User eine spezifische Frage hat, diese gezielt beantworten mit Verweis auf Events UND Code
## Hinweise
- Die Env-Variable `AUTOPILOT_MONITOR_TABLE_CS` enthaelt den Connection String mit SAS Token
- Bei vielen Events (>100) kann die Ausgabe gross werden - fokussiere auf relevante Events, filtere performance_snapshot und agent_metrics_snapshot raus wenn sie nicht relevant sind
- DataJson Felder enthalten oft die wichtigsten Details - IMMER reinschauen bei relevanten Events
- Wenn ein Event unklar ist, schau im Code nach wo dieser EventType erzeugt wird (grep nach dem EventType-String)
- Phase-Enum: Unknown=0, DevicePreparation=1, DeviceSetup=2, AccountSetup=3, DeviceESP=4, UserESP=5, Complete=6, PreProvisioning=7
+52
View File
@@ -0,0 +1,52 @@
#!/bin/bash
# Helper script to query Azure Table Storage via REST API
# Usage: query-table.sh <table_name> <odata_filter> [select_fields]
#
# Requires AUTOPILOT_MONITOR_TABLE_CS environment variable with format:
# TableEndpoint=https://....table.core.windows.net/;SharedAccessSignature=sv=...
TABLE_NAME="$1"
FILTER="$2"
SELECT="$3"
if [ -z "$TABLE_NAME" ] || [ -z "$FILTER" ]; then
echo "Usage: query-table.sh <table_name> <odata_filter> [select_fields]"
exit 1
fi
if [ -z "$AUTOPILOT_MONITOR_TABLE_CS" ]; then
echo "ERROR: AUTOPILOT_MONITOR_TABLE_CS environment variable not set"
exit 1
fi
# Parse connection string
ENDPOINT=$(echo "$AUTOPILOT_MONITOR_TABLE_CS" | sed -n 's/.*TableEndpoint=\([^;]*\).*/\1/p' | sed 's:/*$::')
SAS=$(echo "$AUTOPILOT_MONITOR_TABLE_CS" | sed -n 's/.*SharedAccessSignature=\(.*\)/\1/p')
if [ -z "$ENDPOINT" ] || [ -z "$SAS" ]; then
echo "ERROR: Could not parse connection string (need TableEndpoint and SharedAccessSignature)"
exit 1
fi
# URL-encode the OData filter for use in query string
ENCODED_FILTER=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$FILTER', safe=''))" 2>/dev/null)
if [ -z "$ENCODED_FILTER" ]; then
# Fallback: manual encoding of common OData chars
ENCODED_FILTER=$(echo "$FILTER" | sed "s/ /%20/g; s/'/%27/g")
fi
# Build full URL with query params
QUERY_URL="${ENDPOINT}/${TABLE_NAME}()?${SAS}&\$filter=${ENCODED_FILTER}"
if [ -n "$SELECT" ]; then
ENCODED_SELECT=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$SELECT', safe=''))" 2>/dev/null)
if [ -z "$ENCODED_SELECT" ]; then
ENCODED_SELECT="$SELECT"
fi
QUERY_URL="${QUERY_URL}&\$select=${ENCODED_SELECT}"
fi
# Execute query
curl -s -H "Accept: application/json;odata=nometadata" \
-H "x-ms-version: 2024-11-04" \
--globoff \
"$QUERY_URL"
@@ -57,15 +57,21 @@ export function SectionAnalyzeRules() {
</div>
<div className="px-4 py-4 space-y-2 text-sm text-gray-700">
<p>Inspects a field inside the <code className="bg-gray-100 px-1 rounded">data</code> payload of a specific event type. Use this to match on values like error codes or app names.</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs">
<div className="p-3 bg-amber-50 border border-amber-200 rounded text-xs text-amber-900">
<strong>Important:</strong> The available data fields depend on <strong>which collector type</strong> produced the event.
Structured collectors (Registry, WMI, JSON, XML) expose individual named fields.
<strong>Command</strong> collectors produce only raw text — use <code className="bg-amber-100 px-1 rounded">dataField: &quot;output&quot;</code> with <code className="bg-amber-100 px-1 rounded">contains</code> or <code className="bg-amber-100 px-1 rounded">regex</code> to search the command output.
See the <em>Data fields by collector type</em> table in the Gather Rules section for a full reference.
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 text-xs mt-2">
<div>
<p className="font-medium text-gray-900">Operators</p>
<ul className="mt-1 space-y-0.5 text-gray-500">
<li><code className="bg-gray-100 px-1 rounded">equals</code> — exact match</li>
<li><code className="bg-gray-100 px-1 rounded">contains</code> — substring match</li>
<li><code className="bg-gray-100 px-1 rounded">regex</code> — regular expression match</li>
<li><code className="bg-gray-100 px-1 rounded">contains</code> / <code className="bg-gray-100 px-1 rounded">not_contains</code> — substring match</li>
<li><code className="bg-gray-100 px-1 rounded">regex</code> / <code className="bg-gray-100 px-1 rounded">not_regex</code> — regular expression match</li>
<li><code className="bg-gray-100 px-1 rounded">gt / lt / gte / lte</code> — numeric comparisons</li>
<li><code className="bg-gray-100 px-1 rounded">exists</code> — field is present with any value</li>
<li><code className="bg-gray-100 px-1 rounded">exists</code> / <code className="bg-gray-100 px-1 rounded">not_exists</code> — field presence check</li>
</ul>
</div>
<div>
@@ -75,6 +81,8 @@ export function SectionAnalyzeRules() {
<li><code className="bg-gray-100 px-1 rounded">appName</code> — application name</li>
<li><code className="bg-gray-100 px-1 rounded">exitCode</code> — process exit code</li>
<li><code className="bg-gray-100 px-1 rounded">phase</code> — enrollment phase name</li>
<li><code className="bg-gray-100 px-1 rounded">output</code> — command stdout (Command collector)</li>
<li><code className="bg-gray-100 px-1 rounded">error_output</code> — command stderr (Command collector)</li>
</ul>
</div>
</div>
@@ -82,6 +90,11 @@ export function SectionAnalyzeRules() {
<p className="font-semibold text-green-900 mb-1">Example — detect error code 0x80070002</p>
<p>Source: <strong>event_data</strong> · Event Type: <strong>app_install_failed</strong> · Data Field: <strong>errorCode</strong> · Operator: <strong>equals</strong> · Value: <strong>0x80070002</strong></p>
</div>
<div className="p-3 bg-green-50 border border-green-200 rounded-lg text-xs">
<p className="font-semibold text-green-900 mb-1">Example — check command output for a certificate subject</p>
<p>Source: <strong>event_data</strong> · Event Type: <strong>gather_machine_cert_store</strong> · Data Field: <strong>output</strong> · Operator: <strong>not_contains</strong> · Value: <strong>CN=MyCertificate</strong></p>
<p className="text-green-700 mt-1">Confidence Factor: <code className="bg-green-100 px-1 rounded">gather_machine_cert_store.error_output exists</code> → +10 (command had errors)</p>
</div>
</div>
</div>
@@ -273,6 +286,35 @@ export function SectionAnalyzeRules() {
</div>
</div>
{/* Example 5 */}
<div className="border border-gray-200 rounded-lg overflow-hidden">
<div className="px-4 py-2.5 bg-indigo-50 border-b border-indigo-200">
<p className="font-semibold text-sm text-indigo-900">Example 5 — Analyze Command Output (Gather Rule)</p>
<p className="text-xs text-indigo-700 mt-0.5">Check if a certificate subject is missing from certutil output</p>
</div>
<div className="px-4 py-4 text-xs space-y-2">
<div className="p-2 bg-gray-100 rounded text-gray-600">
<strong>Gather Rule:</strong> <code>certutil -store My</code> → outputs as event type <code>gather_machine_cert_store</code>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
<div><p className="text-gray-500">Category</p><p className="font-medium">identity</p></div>
<div><p className="text-gray-500">Severity</p><p className="font-medium">warning</p></div>
<div><p className="text-gray-500">Base Confidence</p><p className="font-medium">50</p></div>
<div><p className="text-gray-500">Threshold</p><p className="font-medium">40</p></div>
</div>
<div className="bg-gray-50 border border-gray-200 rounded px-3 py-2 font-mono space-y-1">
<p className="text-gray-400">{`// Condition 1 — certutil data was gathered`}</p>
<p>{`source: "event_type" eventType: "gather_machine_cert_store" operator: "exists"`}</p>
<p className="text-gray-400 pt-1">{`// Condition 2 — certificate subject not found in command output`}</p>
<p>{`source: "event_data" eventType: "gather_machine_cert_store" dataField: "output" operator: "not_contains" value: "CN=MyCert"`}</p>
<p className="text-gray-400 pt-1">{`// Confidence Factor (+20 if command produced no output at all)`}</p>
<p>{`signal: "no_output" condition: "gather_machine_cert_store.output not_exists" weight: 20`}</p>
<p className="text-gray-400">{`// Confidence Factor (+10 if command had errors)`}</p>
<p>{`signal: "cmd_error" condition: "gather_machine_cert_store.error_output exists" weight: 10`}</p>
</div>
</div>
</div>
</div>
{/* ── JSON editing tip ── */}
@@ -224,6 +224,17 @@ export function SectionGatherRules() {
</div>
</GuardToggle>
</div>
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg text-xs text-amber-900">
<p className="font-semibold mb-1">Data fields produced</p>
<p className="mb-2">Unlike structured collectors (JSON, XML, WMI) where individual fields are extracted, a command produces <strong>raw text output</strong>. The event data contains:</p>
<ul className="list-disc ml-5 space-y-0.5">
<li><code className="bg-amber-100 px-1 rounded">output</code> — Standard output (stdout) of the command, max 32 KB</li>
<li><code className="bg-amber-100 px-1 rounded">error_output</code> — Standard error (stderr), max 8 KB</li>
<li><code className="bg-amber-100 px-1 rounded">exit_code</code> — Process exit code (0 = success)</li>
<li><code className="bg-amber-100 px-1 rounded">command</code> — The command string that was executed</li>
</ul>
<p className="mt-2">In Analyze Rules, use <code className="bg-amber-100 px-1 rounded">dataField: &quot;output&quot;</code> with operators like <code className="bg-amber-100 px-1 rounded">contains</code>, <code className="bg-amber-100 px-1 rounded">not_contains</code>, or <code className="bg-amber-100 px-1 rounded">regex</code> to inspect the command output.</p>
</div>
<div className="p-3 bg-green-50 border border-green-200 rounded-lg">
<p className="font-semibold text-green-900 text-xs mb-1">Example — TPM at Enrollment Complete</p>
<div className="mt-1 text-xs space-y-0.5">
@@ -407,8 +418,33 @@ export function SectionGatherRules() {
<p className="text-gray-700 text-sm">
Each gather rule execution emits an event with the configured <strong>Output Event Type</strong> and <strong>Severity</strong>.
The collected data is stored in the event&apos;s <code className="bg-gray-100 px-1 rounded">data</code> field as key-value pairs.
These events appear in the session timeline.
These events appear in the session timeline and can be inspected by <strong>Analyze Rules</strong> using the <code className="bg-gray-100 px-1 rounded">event_data</code> condition source.
</p>
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg text-sm text-amber-900">
<p className="font-semibold mb-2">Data fields by collector type</p>
<p className="text-xs mb-3">Each collector type produces different data fields. When writing Analyze Rules, you reference these fields via <code className="bg-amber-100 px-1 rounded">dataField</code> in <code className="bg-amber-100 px-1 rounded">event_data</code> conditions.</p>
<div className="border border-amber-200 rounded-lg overflow-hidden">
<table className="w-full text-xs">
<thead className="bg-amber-100/50 border-b border-amber-200">
<tr>
<th className="px-3 py-1.5 text-left font-semibold">Collector</th>
<th className="px-3 py-1.5 text-left font-semibold">Data Fields</th>
</tr>
</thead>
<tbody className="divide-y divide-amber-100">
<tr><td className="px-3 py-1.5 font-medium">Registry</td><td className="px-3 py-1.5">Value names as keys (e.g., <code className="bg-amber-100 px-1 rounded">DisplayName</code>), or <code className="bg-amber-100 px-1 rounded">subkey_count</code> + <code className="bg-amber-100 px-1 rounded">subkey_0</code>, <code className="bg-amber-100 px-1 rounded">subkey_1</code>, …</td></tr>
<tr><td className="px-3 py-1.5 font-medium">Event Log</td><td className="px-3 py-1.5">Event properties as key-value pairs (varies by event)</td></tr>
<tr><td className="px-3 py-1.5 font-medium">WMI Query</td><td className="px-3 py-1.5">WMI object properties as keys (e.g., <code className="bg-amber-100 px-1 rounded">Name</code>, <code className="bg-amber-100 px-1 rounded">Status</code>)</td></tr>
<tr><td className="px-3 py-1.5 font-medium">File</td><td className="px-3 py-1.5"><code className="bg-amber-100 px-1 rounded">exists</code>, <code className="bg-amber-100 px-1 rounded">path</code>, <code className="bg-amber-100 px-1 rounded">size_bytes</code>, <code className="bg-amber-100 px-1 rounded">content</code> (if readContent is true)</td></tr>
<tr className="bg-amber-100/30"><td className="px-3 py-1.5 font-medium">Command</td><td className="px-3 py-1.5"><code className="bg-amber-100 px-1 rounded">output</code> (stdout), <code className="bg-amber-100 px-1 rounded">error_output</code> (stderr), <code className="bg-amber-100 px-1 rounded">exit_code</code>, <code className="bg-amber-100 px-1 rounded">command</code> — raw text only, no parsed fields</td></tr>
<tr><td className="px-3 py-1.5 font-medium">Log Parser</td><td className="px-3 py-1.5">Named capture groups from regex (e.g., <code className="bg-amber-100 px-1 rounded">appName</code>, <code className="bg-amber-100 px-1 rounded">action</code>)</td></tr>
<tr><td className="px-3 py-1.5 font-medium">JSON</td><td className="px-3 py-1.5">Matched JSON values as structured key-value pairs</td></tr>
<tr><td className="px-3 py-1.5 font-medium">XML</td><td className="px-3 py-1.5">Matched XML elements/attributes as key-value pairs</td></tr>
</tbody>
</table>
</div>
</div>
</section>
);
}
File diff suppressed because one or more lines are too long