mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
main
99 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7a917eeaee |
feat: a session the platform blocked says so (D-359)
When the event limit or the platform operator blocks a session's device, the session now carries a block record: Block* fields on the session and its index row, a server-authored device_blocked timeline event, an interim analyze run for that event and, on the auto path, an audit entry. The block list itself stays platform-only. - Portal: a card above the failure snapshot says the monitoring was paused (orange) or ended (red), when, until when and, for the event limit, the session's event count against the limit. The Blocked pill reads the record for every role; Global Admin mode still adds the live block list. - A Kill is device-wide until it expires, so its record carries the end and the card says new enrollments on the device are not monitored until then. - Status: the stalled and timeout reasons name the block instead of a silent agent; the classification is unchanged. - Rules: ANALYZE-ENRL-006 explains the pause right after it (on_event device_blocked); ANALYZE-ENRL-002 no longer fires for a blocked session. - MCP: the coverage block of get_session_summary names the block as a gap. - The agent-facing block message no longer says "by an administrator". - BlockCause == event_limit replaces ExcessiveEventsAutoActioned as the auto-action's idempotency marker. - Privacy page: the event limit next to rate limiting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e5cb3c4f5e |
Rules: every analyze rule carries a presentation block (D-356)
All 54 shipped analyze rules get role, summary, subject and key facts as
{{signal.key}} templates, each with a minor version bump so the self-seed
picks them up. Values were checked against stored findings, the rows that
receive the block at read time.
- ENRL-001 repeats the outcome only: the 0x87D1041C paragraph and the
placeholder note are gone, labels read "ESP step that failed" / "ESP
error code", remediation is "Find the cause". This changes its alert text.
- ENRL-002 says the sweep "closed" the session (it can end Incomplete).
- ID-002 and ID-004 gain evidence-only conditions (unexpected account
names; IME token failure count and codes); firing and confidence are
unchanged.
- Resolution: facts sharing a label are alternatives, an exact repeat of
label and value is left out (new shared fixture case, web module and
its generated MCP copy).
- Ratchet: every built-in presentation passes the save validator, uses
qualified tokens only, and each token names a condition of the rule and
a key its source records (RuleTemplateInterpolator.TokenRegex internal).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
e2e12055bf |
Show the NTP clock skew in the right direction
The agent's ntp_time_check reports offsetSeconds = NTP time - device time, so a positive value means the device clock is BEHIND. The session card said "ahead of UTC" for it (a device 10 h behind showed as 10 h ahead), and ANALYZE-DEV-007 named its two signals the wrong way round. - SessionInfoCard: the direction comes from deviceClockDirection (sessionInfoLogic.ts, pinned by a test with both signs). - ANALYZE-DEV-007 1.1.1: offsetSeconds > 120 is clock_behind, < -120 is clock_ahead. Conditions, thresholds and text are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
46058ffb90 |
OOBE quality update: how it ended, a begin without the page start, three rules for the update that did not happen (D-320)
Field measurement 3: a first-run agent that started after the update page (6a7d241d) left the whole update in Apps (ESP), failed and skipped updates were reported by nothing, and the lane listed a failed update's staged servicing stack as an installed KB. - Time attribution v5: update names before the first observed OobeNDUP start begin the update at the earlier of the first name and the update's first servicing step (after the window's ESP exit when one was seen before the name, else the first step of a package the update also serviced later). The fallback's ESP exit is per window, so a WhiteGlove part-1 exit no longer begins an update in part 2. OsUpdateSpan carries Outcome (installed/failed/skipped/unknown, the latest evidence wins) and NotInstalledKbs; Kbs lists only packages that reached Installed. The 2-h sweep recomputes the last 30 days. - ANALYZE-DEV-013 (update failed), DEV-014 (not offered after an earlier failure), DEV-015 (update check ended without a result): warning, never KO; a failure only from explicit names, every absence condition gated on the agent's names_capped marker; never two at once. - ShellCoreTracker rows never set the session start (D-315 list), ahead of the agent's first-run update page backfill. - The web lane names the outcome and the KBs not installed; MCP get_time_attribution mentions both. Real-event replay: 6a7d241d os_update 2358 s installed (was 55 min in esp_apps), e7551b82 failed with KB5124007 not installed. Rule dry runs and a sweep over all 97 update-page sessions since 2026-10-02 hit exactly the five field sessions. Functions 6,918, web 1,701, MCP 997 green. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
08455cc5f9 |
OOBE quality update: install events from the System log, servicing steps, a bounded engine phase in Shadow mode (D-310)
Report 3a2207978e4c: a session was failed 30 min after the ESP exit while Windows installed its OOBE quality update. The update watcher had been blind since July and the engine had no notion of the phase. - Windows Update client: 19/20/43/44 come from the System log (provider-filtered XPath), as the provider manifest routes them; 25/26/31/41 from Operational via their own config list. Updates are classified os/defender/store/other: only Windows/.NET updates become rows, the rest is counted into windows_update_activity_summary. Per-channel watermarks persist a record only after its row and engine signal were handed to the ingress. - Servicing: CBS steps from the Setup log (windows_update_servicing), client UpdateAgentLCU. - Engine: OsUpdateActivity and OsUpdateFacts (event-time ordered) anchor an OOBE update phase after the ESP exit. Each state has its own bound: the update runs at most 3 h (then Failed oobe_update_not_finished), after it the user has 60 min to sign in (then the engine stops waiting without a verdict), after the sign-in one window of grace. - The phase acts only in OobeUpdatePhaseMode=Active. Shadow, the default, reports what it would decide as oobe_update_phase_shadow right before today's verdict. The agent stamps the mode on EnrollmentFactsObserved at every start; a later stamp can take Active away, never grant it. - Agent: a stop without a verdict ends the session like the max-lifetime watchdog (agent_shutting_down reason=stopped_waiting); the backend classifies it honestly (verdict origin stopwait), never Failed. - Measurement: oobe_update_page (the update page and its ExpeditedUpdate_* names; a 62407 value only as boolean, integer or HRESULT) and oobe_update_state (NDUP and ESP-policy registry values), both Debug and bounded per run. - ANALYZE-DEV-004 1.2.0 counts install and download failures only, never scan failures. - Event types in Constants, MCP catalog and web; shared manifests regenerated; privacy page names Windows updates installed during the enrollment. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
0d6c03d64b |
Public texts no longer name a customer domain or IME decompiles
- Two code comments of the gather-rule timeline feed named a customer's domain next to the session id; the session id stays as the regression anchor. - The get_ime_pattern_health description and the IME Pattern Health admin text no longer send the reader to "the IME decompile"; the IME-TOKEN-FAILURE and IME-DO-TEL pattern descriptions say "verified against IME x" / "absent from the x builds" (rules/dist regenerated). - Code comments that cited decompiled IME builds cite the IME version instead. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
106480ef52 |
fix(agent,rules): health-script lines never feed a platform-script slot — PS-SCRIPT-EXITCODE deleted, HS-INVOCATION opens an unowned invocation in the IME log, the no-marker fallback stays within files platform runs open in (D-248)
Test-VM session 24dc69d1 (2.0.1459) had the overwrite geometry the result hold (D-247)
was built for, yet the hold never fired: "Powershell execution is done, exitCode = 0" —
written by IME's health-script worker (AgentCommon ScriptWorker) to both
IntuneManagementExtension.log and HealthScripts.log — was read as PS-SCRIPT-EXITCODE and
attributed to the pending platform script c81b8053 1.2 s before its executor even started
(IME-log marker: the last PS-SCRIPT-GENERATED; HealthScripts.log: the no-marker fallback to
the last platform start). The completion went out at the result line with the foreign exit
code, and the run's own end block, recovered 150 ms later by the rewind, was dropped as
already emitted. Both codes were 0 by luck; a failing health script would have turned a
succeeded platform script into script_failed. The worker's launch line flipped the pending
script's context the same way (338d9434, twice in one run).
- rules: PS-SCRIPT-EXITCODE deleted — the line is health-script-only (3 hits = 3 cycles,
platform scripts never produce it), HS-EXITCODE already carries the value, and a line that
lands inside a parallel platform run's window cannot be attributed by position.
HS-INVOCATION (new): the worker's command line `"…\agentexecutor.exe" -remediationScript`
(identical from IME 1.104.102 to 1.105.152) as agentInvocationArgument of a non-powershell
kind — the launch line it writes next stays with that invocation. Both take effect on the
next reseed for every agent from 2.0.1458 on.
- tracker: a file without markers resolves to the platform run in flight only when a
platform run has ever opened in that file (IntuneManagementExtension.log after a rollover,
AgentExecutor.log); HealthScripts.log and the app logs never qualify. Test seams:
ProcessLogMessageForTest takes the source file, ClearInvocationMarkersForTest.
- tests: ImeLogTrackerPlatformScriptContaminationTests +5 with the session's real lines —
the worker's exit line pre-fills nothing, its launch line flips no context, HealthScripts.log
lines resolve to no platform script, the context line after an IME-log rollover still finds
the run in flight, and no shipped pattern reads the worker's exit line.
- rules/dist regenerated (node rules/scripts/combine.js); analyze-rules.json catches up with
ANALYZE-OFFICE-001 1.1.1 (
|
||
|
|
197de1d718 |
fix(agent): the IME log tracker verifies and rewinds when a concurrent IME process overwrote bytes behind its bookmark; executor lines belong to the invocation at their file position (D-246)
IME's trace listener opens every log once per process with FileMode.Append and FileShare.ReadWrite and writes at its own stream position afterwards. AgentExecutor.exe (two processes per user-context script, one per detection script) has such a listener on AgentExecutor.log and on IntuneManagementExtension.log, so a process that resumes logging after another one appended overwrites those bytes: the final file holds the last writer, a byte-position tailer has read the first version. Session 46749560 lost 6 of 16 platform-script results and 4 of 16 executor end blocks with every tracker health counter at zero — results fell back to the exit code (one Success where IME said Failed), one finished script stayed Incomplete. The agentexecutor_fallback results in the fleet are this race, not late IME logging. The tracker now keeps a fingerprint ledger of every entry it processed from the two multi-writer files (FNV-1a 64 over the decoded bytes, one CmTraceEntryAssembler shared by the read loop and the verification so entry boundaries are identical by construction), verifies it on every script end signal, on a byte-budgeted cadence while an invocation is in flight (256 KB/s, at least 1 s apart), in a 4 KB guard zone on every growth pass and on a fragment at the bookmark, and rewinds to the first changed entry; entries whose (offset, hash) still read unchanged are skipped, everything else is processed as backlog (never fresh, never a calibration anchor). The policyId-less executor lines are owned by the nearest preceding invocation marker in their file (PS-AGENT-ARG opens, PS-AGENT-SCRIPT-START upgrades to the platform script, PS-AGENT-INVOCATION and PS-AGENT-COMPLETED close) instead of a last-started pointer, so an end block a rewind uncovers reaches its own script. Restart seeds the ledger from the 64 KB behind the persisted bookmark; a rollover clears it. Four restart-safe counters (ime_overwrite_rewinds, ime_overwrite_bytes_reprocessed, ime_verify_passes, ime_verified_bytes) ride agent_metrics_snapshot and ime_pattern_hits — recovered data, not skipped work, no new event type. Tests: ImeLogTrackerOverwriteRewindTests (15) drive the positional-overwrite write pattern against both files, including the two field scenarios with the shipped patterns; agent suite 2921 green, backend known-actions list extended for the two new patterns. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
61892592e4 |
feat(rules): community rule submissions — submit custom rules from the portal, review via GA page or MCP, publish by repo commit + reseed (D-236)
Tenant admins submit references to their own gather/analyze rules; the server freezes the rule, records pre-flight findings and fire stats, and stores it under a 12-character id (RuleSubmissions, SessionReports layout). Attribution is the submitter's frozen choice (anonymous by default) and becomes the published author. Review (approve with reserved id / decline with comment) through /admin/reports/rule-submissions or the MCP tools list_rule_submissions, get_rule_submission, review_rule_submission, reseed_rules_from_github; published is derived from the global catalog, never stored. Analyze rules now get the same server-side author stamp as gather rules; ANALYZE-ID-001 carries author Community. Privacy page names the credit; bumps internal and .claude/commands gitlinks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
8224c8f1a8 |
fix(rules,engine,agent): ESP-004 gates on the esp_apps_timeout verdict; not_exists absence gate; Shell-Core failure inherits settle detail (D-234, D-235)
ANALYZE-ESP-004 fired on every ESP terminal failure of a Continue-anyway profile and rendered the configured FirstSync limit as elapsed time — the mayHaveContinuedAnyway flag only says the profile has the button. The rule now requires the agent's own verdict (app_install_failed with failureType=esp_apps_timeout), names that app via a same-event filter, reads failedSubcategory from esp_failure_settle_started and words the limit as a limit. Verified with test_analyze_rule: cb7036a6 fires, 683f1eff (0x80070652 after 19 min) stays silent. Rule engine: not_exists with a dataField on event_type/event_data conditions matches when no event of the type carries a non-empty value — including an absent type — instead of being dead behind the fieldValue null short-circuit. Schema and MCP authoring guide say so. Agent: a Shell-Core 62407 failure that lands inside the provisioning tracker's 30-s settle window (a third of all ESP terminal failures) now inherits the pending errorCode / failedSubcategory / category / likelyCulpritApps, so enrollment_failed, Sessions.FailureReason and the termination-time app classification see the HRESULT the registry had already named. failureType stays the Shell-Core value. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
66452621c6 |
exit-code class per app + reboot rule: IME-EXITCODE-CLASS, ANALYZE-APP-018, exitCode on every terminal app event (D-214)
- IME-EXITCODE-CLASS captures the return-code class of the installer exit code ("lpExitCode is defined as <Success|SoftReboot|HardReboot|Retry|Failed>", verified against the IME string literal) as exitCodeClass on AppPackageState; new action captureExitCodeClass, persisted across the agent restart a reboot causes, last value wins across a Retry loop. IME-EXITCODE-MAPPED-FAILED keeps the error path; both match the same line.
- Adapter: exitCodeClass whenever known, exitCode on every terminal state (Installed/Postponed/Skipped/Error) instead of Error only.
- ANALYZE-APP-018 (warning, apps): app_install_completed.exitCodeClass in SoftReboot,HardReboot; +25 HardReboot, +15 observed system_reboot_detected; never fails the session.
- Web: completed-app exit badge amber (failed stays red), "mapped as SoftReboot/HardReboot/Retry" on timeline and Install Progress.
- Tests: pack end to end (phase, current app, class lines, negatives, restart round trip), adapter payloads, linearity samples, backend rule engine + known actions.
- internal gitlink: plan, D-214, backlog.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
||
|
|
d2197c6a57 |
error-code catalog v2: generated from primary sources, symbols and families, MCP lookup_error_code (D-211/D-212)
Shared: error-codes.json schemaVersion 2 (607 entries, 50 IME enforcement states) generated by scripts/error-codes/generate.mjs from MS Learn tables and the IME enums of one build; ErrorCodeEntry gains symbol/category/imeRetriesDuringEsp, ErrorCodeCatalog gains TryLookupDetailed (HRESULT_FROM_WIN32 derivation as data), TryLookupBySymbol, TryLookupEnforcementState. Backend enricher siblings carry symbol/category/derivedFromWin32 plus enforcementStateInfo. Web: validating, minifying sync; formatErrorCodeSource for every tooltip; windows_update_failed badge; script error codes decoded; bundle budget test. Agent: WindowsUpdateTracker reads the catalog symbol (WU_E_UNKNOWN sentinel, load failure never costs an event). MCP: error-code-catalog.ts (lexical-only), lookup_error_code for every role, search_knowledge merges catalog hits, get_session_summary key events carry errorCode/errorText, Dockerfile COPY + ERROR_CODES_PATH. ANALYZE-APP-014 1.2.1 corrects the 0x80073cf0 gloss. Gitlinks: internal (D-211/D-212, plan review, backlog), .claude/commands. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
f0a3f04398 |
fix(agent,rules): AccountSetup activity means observed progress; same-event filter for event_data conditions (q8n)
Agent: ProvisioningStatusTracker.HasAccountSetupActivity now requires at least one AccountSetup subcategory past notStarted. Windows writes AccountSetupCategory.Status with every subcategory notStarted BEFORE the Device-ESP page exits, so the presence check confirmed the Device->Account handoff (Shell-Core 62407) as the final ESP exit; the user-apps-settled synthesis then completed AccountSetup and released the User-ESP keep-awake minutes before the real exit (fleet: 77 % of keep-awake sessions, median 3 min; on battery that is enough for Modern Standby). D-183. Rule engine: filterField/filterOperator/filterValue now apply to event_data and event_data_array as a same-event filter (shared ApplyValueFilter with event_count). ANALYZE-DEV-012 v1.1.0 excludes backfilled pre-session episodes (backfilled equals false); a second independent condition could not, since each condition scans all events on its own. D-184. Schema, Shared model, MCP authoring guide and generated types follow. Tests: ProvisioningStatusTrackerAccountSetupActivityTests (the verbatim all-notStarted registry JSON), SleepEpisodeRuleTests (+3 backfill cases), RuleEngineEventDataFilterTests. Solution build 0 warnings; agent suite 2820 and backend suite 5791 green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
db020789e1 |
chore: private submodule docs/ becomes internal/ (knowledge + decisions + work files + private instructions)
- .gitmodules: docs -> internal, repo renamed to Autopilot-Monitor-internal; the OKF bundle now lives at internal/docs/ - .claude/CLAUDE.md rewritten: where knowledge lives, working-file rules, hard rules; imports the private half from internal/CLAUDE.md (silently absent without submodule access) - .claude/architecture-principles.md moved into the internal bundle (reconciled against the code) - tasks/ (gitignored) retired; working files live in internal/work/ - doc path references docs/<bundle> -> internal/docs/<bundle> in code comments, skills gitlink, codeql paths, build script - rule descriptions no longer cite an internal doc path (customer-visible text); rules/dist regenerated - public tree no longer names private operator tooling - .claude/commands gitlink: skill docs point at the new paths |
||
|
|
f4a5eac539 |
chore(rules): IME pattern-pack hygiene against the IME decompiles (1.97.107.0 / 1.104.102.0 / 1.105.103.0) — no wording drift in any shipped pattern; the 11 disabled legacy patterns whose literals last existed in IME 1.50 (fleet minimum 1.97) are deleted instead of left as re-enable traps (IME-DETECTED-OLD, IME-SET-CURRENT-1/2/3, IME-SKIP-APPLICABILITY-NOTMET, IME-SKIP-DEPENDENCY-DETECT, IME-SKIP-DETECTED, IME-SKIP-UNINSTALL-NOTDETECTED-2, IME-UPDATE-NAME-1/2/3); IME-DO-TIMEOUT-1/2 match the exact source literal "DO downloading is not finished within timeout, jobId = …, fileId = …" instead of two lookaheads; IME-DO-TEL documented as emitter-less from 1.104 on and kept only for the 1.101/1.103 fleet share
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
09492e908c |
fix(security): IME log-pattern pack is anchored and linear (all 90 patterns start with ^, IME-APP-VERSION spells the report-delta JSON out instead of a chained [^}]*? scan) and the ImeLogTracker read loop is bounded — BoundedLineReader (32 MB per line, evidence: real 5.6 MB "Get policies" lines; exact byte bookmarks, no decoder read-ahead loss on cancel), 2 s per-line matching budget across all patterns, held-back unterminated EOF tails (≤1 MB, 1 s settle) instead of dropping them and raw-matching fragments, a complete entry after a dropped one is no longer swallowed as its close, one Warning per pass (oversizedLines/regexTimeouts/lineBudgetBreaks) instead of silent Debug timeouts; unanchored PS-SCRIPT-CONTEXT false positives on [Win32App] SideCarScript… lines (which overwrote the pending platform script's RunContext) are gone; --run-ime-matching assembles multiline entries like the tracker
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
84d06e1cf6 |
fix(security): LocalAdminAnalyzer never exempts a logged-in local SAM account from the shutdown scan (CWE-807) and matches built-ins by well-known RID so localized Guest/Administrator are not flagged
The shutdown run allowed every logged-in user (explorer.exe owner) dynamically, so a backdoor account created after the startup scan and signed in before enrollment completed was scored 0. The WMI inventory is now read first and a logged-in name that is a local account is rejected for the account and the profile check (reported as logged_in_local_accounts); only Entra/domain identities — the only ones seen in real sessions — remain dynamically allowed for their profile folder.
Built-ins are recognised by RID 500/501/503/504 on the machine SID in addition to their English names: since disabled accounts are inventoried (
|
||
|
|
88dfd331dd |
fix(security): LocalAdminAnalyzer inventories disabled accounts and reads Administrators-group membership from the SAM (NetLocalGroupGetMembers on S-1-5-32-544); a dormant /active:no admin backdoor now surfaces as unexpected_admin_members (+40, Error), built-in Administrator enabled-state reported for evidence
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5055db2de1 |
feat(rules): detect installer-mutex app failures (0x80070652) and SWG-blocked diagnostics uploads
Two analyze rules born from the pilatus-aircraft diagnosis (2026-08-27): - ANALYZE-APP-017 "App Install Blocked by Another Installer (0x80070652)": fires on app_install_failed with hresultFromWin32 -2147023278 (ERROR_INSTALL_ALREADY_RUNNING); confidence boosts for MSI exit code 1618 and for an ESP failure settling with the same code. The 08-26 burst failed 16 sessions deterministically at ~21 min (IME's fixed msiexec-busy retry budget) right after a preceding installer left the Windows Installer mutex held - previously only generic "Enrollment Failed" fired, naming no cause. - ANALYZE-NET-001 "Diagnostics Upload Blocked by Web Security Gateway": fires on diagnostics_upload_failed answered by an HTML block page carrying a recognizable security-product marker (McAfee/Skyhigh, Zscaler, Netskope, ...). OnFailure diagnostics are lost exactly on the sessions that need them; plain HTML errors without a vendor marker (stale/stopped endpoint) stay excluded on purpose. Both validated via MCP validate_rule (0 errors) and dry-run with test_analyze_rule: fire on the burst session (confidence 100 / 90), silent on a timeout-shaped failure and on a succeeded session that carried unrelated app_install_failed events. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
c91d8d4501 |
fix(agent): complete enrollments with zero user-targeted apps instead of failing them
IME never writes its "Completed user session" line when the user has no assigned Win32 apps - its zero-app branch returns before that statement (ApplicationPoller.cs:713-721, identical in 1.80/1.83/1.97/1.104). So ImeUserSessionCompletedUtc stayed null, every arm of ShouldTransitionToAwaitingHello was unsatisfiable, and the 30-min advisory_completion backstop resolved healthy enrollments as esp_exit_without_completion_evidence. One tenant: 10 of 30 sessions in a week, all false positives - DAD-validated desktop, Hello provisioned, RealmJoin still installing. Every Succeeded session there carries ime_user_session_completed; not one of the failed ones does. IME-USER-SESSION-ZERO-APPS reads the line IME does write there and routes it through the same OnUserSessionCompleted callback. That is IME's own verdict rather than a weakened gate: the zero-app branch locks the ESP in with an empty application list, and the sibling emit site returns ProvisioningComplete right after logging it. Guarded to the AccountSetup phase so it cannot burn the adapter fire-once flag on a pre-sign-in timestamp, and replayed on the phase transition when the observation predates the phase marker. AreUserEspAppsSettled accepts an empty app list only with that explicit observation, never on emptiness alone. Second, independent defect: advisory_completion (30 min) overran the RealmJoin completion gate, which deliberately holds the session and carries its own bounded resolution (60 min from detection, activity-extended, 4 h cap). One session installed 25 RealmJoin packages inside the window, the last 2 min before the failure, while completion_waiting still read "waiting on: realmjoin_resolution". The handler now re-arms while that gate is closed and its own deadline is still armed; convergence comes from the gate itself, which always ends with an Outcome set. ime_user_session_completed gains an evidence field (zero_user_apps_enumerated / user_session_completed) - no new event type, so the Constants -> resource-catalog -> shared-manifests triple stays untouched. The pattern ships via the config API and reaches existing agents, where an unknown action is a single Debug line; the behaviour needs the agent release. Build 0 warnings. DecisionCore 558/558, V2.Core 2530/2530, Backend 4634/4634, MCP event-types-drift 6/6. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
06cf1c4c12 |
feat(agent): system timeline watcher — clock steps + sleep episodes as ground truth
Two enrollment blind spots become timeline ground truth from the System event log via one EventLogWatcher (WindowsUpdateTracker pattern, 24h backfill with RecordId watermark recovering pre-agent records): - system_clock_changed (Kernel-General 1): oldTime/newTime/signed timeDeltaMs, reason and the setting process. 2s floor suppresses w32time 1ms micro-slews; steps >= 5min are Warning + immediate upload. Payload instants are the authoritative source (event timestamps can be clamped server-side). - system_sleep_episode (Power-Troubleshooter 1 classic sleep/hibernate, Kernel-Power 507 Modern Standby; 506 deliberately unconsumed): kind, enteredAt/exitedAt, durationSeconds, wake source / AC state. 60s real-sleep floor filters Modern Standby screen-off scenarios. ConfigVersion 39 (Collectors.EnableSystemTimelineWatcher + SystemEventBackfillLookbackMinutes). Both types are non-activity on agent and backend (idle/stall clocks unaffected; a sleeping device stays Stalled). Consumers: TimeJumpBadge gains the ground-truth cause "clock-set" (classifyTimeJump matches backward display steps against recorded clock sets); EventTimeline badge blocks for both events plus the previously bare power_state_change; SessionInfoCard keeps the wall-clock duration and adds a standby note; TimeAttributionCalculator builds SleepSpans (RebootSpan clone, AttributionVersion 3) persisted as SleepSeconds/SleepSpansJson and rendered in TimeAttributionLane; ANALYZE-DEV-012 (info, single) fires once per session for episodes >= 5min - deliberately the only new rule, clock findings stay with DEV-008 to avoid duplicate-finding flood. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8f24876682 |
feat(agent): live power-state watcher + battery analyze rules DEV-009/010
Event-driven AC/battery observation during enrollment (no polling): a WMI Win32_PowerManagementEvent (EventCode 10) push subscription re-probes GetSystemPowerStatus and diffs snapshots through a pure tracker into power_state_change events - transition ac_to_battery (Warning, immediate) / battery_to_ac (Info) / threshold_crossed at 50/30/15% (per-level lifetime latch, lowest-crossed-only on jumps, arm-time baseline covers sessions starting low, 5s trailing-edge debounce, emission cap 20). Never armed on devices without a battery; arm failure surfaces as collector_degraded. Kill-switch Collectors.EnablePowerStateWatcher (ConfigVersion 38). power_state_change is classified non-activity on both sides (SignalActivityClassifier + IsPeriodicOrStallEvent) so battery drain can neither keep idle collectors alive nor heal a Stalled session. New analyze rules, both with on_event:power_state_change interim triggers and deliberately single-required-condition (event_data conditions scan events independently - no same-instance join): - ANALYZE-DEV-009 (high): thresholdPercent equals 15 - ANALYZE-DEV-010 (warning): transition equals ac_to_battery Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ab61839685 |
feat(backend): P6 split - CMTrace skew tripwire + customer clock_skew rule
Operator side: CmTraceSkewTripwire piggybacks on the terminal counter
reconcile's partition scan (zero extra reads) and compares median
ReceivedAt-OccurredUtc deltas of IME-derived vs other events. A clean
15-min-grid divergence (residual < 2 min, >=20 samples / >=3 distinct
batches per side) emits the CmTraceTimeSkewRegression ops event
(Agent/Warning, goal: never fires). Bias-dominated sessions are
suppressed; measuredWriterOffsetMinutes is never consulted (sticky
after era flip-backs). Kill switch: CmTraceSkewTripwireDisabled.
Customer side: new clock_skew analyze condition source (skewMetric
clock_jump | sustained_offset, threshold seconds, gt/gte magnitude)
behind built-in ANALYZE-DEV-008 (300 s = the Kerberos/Entra 5-min skew
boundary). Batch-median frames with spool-spread filter, plateau step
detection (a gradual backlog-drain ramp cannot fake a jump) and
end-state persistence; IME-derived events are excluded so a CMTrace
anchoring regression can never fire the customer rule. Evidence carries
field=skewSummary/value for {{skewSummary}} interpolation.
Full mirroring chain: schema enum + skewMetric property, shared
manifest + combine.js regeneration, MCP authoring guide + semantic
lint, web SOURCES/form/card, ops-event dual-register, docs. P5 is
subsumed by the tripwire. PercentileMath is now the single nearest-rank
implementation (Apps/PlatformMetrics delegate).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
e1b03de5b3 |
fix(agent): recover ESP completion after a forced mid-ESP reboot
Seven sits-d Cloud PCs enrolled the same day with identical configuration (~138 required apps at the user-ESP gate, DeviceGuard/DmaGuard device-assigned). The two that finished AccountSetup before the coalesced reboot landed succeeded; the five the reboot killed in Stage=EspAccountSetup never derived a completion again and ended Incomplete on healthy devices. Two defects behind that: ShellCoreTracker.BackfillRecentEspExitEvents() has existed since session 772fe502 but no caller ever invoked it, so the Shell-Core watcher only saw records written after Start(). Every agent restart silently dropped an ESP exit (62407) or Hello-wizard start (62404) from its downtime. EspAndHelloHost.Start() now calls it, and the fixed 5-minute window is sized to the actual downtime when the previous run died to a reboot (LastBootUtc, clamped to 360 min). The user-apps-settled synthesis ran only on the EspExited edge. When the ESP page exits with apps still in flight the single attempt misses and nothing re-checks — 8110e262 and a89aac2d both reached 138/138 apps, 0 failed, and still never completed. The exit edge is now recorded and the synthesis re-evaluated from ImeLogTracker.OnAppStateChanged. No new completion path: the gate conditions are unchanged, this only gives the existing conservative check a second look. The fire-once claim moves to Interlocked (edge and re-check run on different threads) and the re-check does not re-emit app_install_starved. Rules: ANALYZE-ESP-005 gains on_event:session_stalled so a hung session produces a finding at the 60-minute stall instead of staying finding-less for hours (verified against 8110e262 — fired, confidence 95). New ANALYZE-APP-016 turns the already-emitted app_install_starved events into a customer finding and, unlike ANALYZE-APP-015, fires even on a succeeded session: 3b9291aa was recorded Succeeded with 99 required apps that never started installing. ANALYZE-APP-015 and ANALYZE-ESP-001 were considered for the stall trigger and deliberately left alone — APP-015 is the documented worked example of the terminal-precondition trap, and ESP-001's phase_duration gate on a still-open phase is unverified mid-run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2cf7216be5 |
Token-failure grace window + registry_app_baseline summary
Live findings from the first two 2.0.1402 sessions (Oliver, 2026-08-18): 1. "Failed to get AAD token" is logged routinely during the device phase (no user token exists yet) - both test sessions raised the Warning although nothing was wrong. The failure is now held pending: any IME-TOKEN-SUCCESS match clears it (staleness-gated so a replayed success from a previous enrollment cannot mask a current outage), and ime_token_failure fires only when no success follows for the 30-min grace window, then deduped per errorCode. Genuine auth outages run long enough to hit the window; routine device-phase failures never do. 2. New one-shot registry_app_baseline event (Info, only when non-empty): summary of the Win32Apps state that predates the agent run - counts by enforcement class + capped 50-app typed payload incl. LastUpdatedTimeUtc. On WDP the DPP-policy apps install in Batch 1 before the bootstrap-script agent exists; their outcomes live only in this baseline, closing the pre-agent app gap without log backfill. The diff rail stays silent on baseline state (anti-replay unchanged). Event type dual-registered (Constants + MCP catalog + manifests regen). Tests: grace-window cycle (resolved / unresolved / re-arm dedup), baseline summary (counts, one-shot, empty). All suites green (Agent 2274, Functions 4262, MCP 561, web 762), 0 warnings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
68eb2da283 |
IME tracker: tail AppActionProcessor.log; GRS + token-failure signals
P0b/P1 of the 2026-08-17 IME code audit (agent release required):
- Tail AppActionProcessor.log (+ rollover glob) in ImeLogTracker and the
--run-ime-matching CLI. IME routes every [Win32App][ActionProcessor]
line there (enforcement decision engine), so IME-NO-ACTION and
IME-NOT-TARGETED-1/2 could never fire before, and GRS skips were
invisible. Live session 0fe2c562 carried 16 no-action and 64
classification lines in that file.
- New patterns (wordings verified in decompiled IME 1.97 AND 1.104):
IME-GRS-SKIP ("is in GRS. The app will not be enforced." -> Postponed;
THE canonical silent non-install reason), IME-GRS-CLEARED (early
subgraph termination, retry next check-in -> Postponed),
IME-TOKEN-FAILURE ("Failed to get AAD token ... errorCode = N").
- New action imeTokenFailure -> ime_token_failure Warning event (new
EventType: Constants + MCP resource-catalog + shared manifests
regenerated). Tracker dedups per distinct errorCode; included in
the historic-replay guard so stale lines from a previous enrollment
cannot emit a misleading warning. Complements IME-TOKEN-SUCCESS:
an auth outage previously left check-ins silently doing nothing.
Tests: adapter emission + unknown-code + tracker dedup; known-actions
catalog extended; all suites green (Functions 4262, Agent 2252, web 762).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
ccf4f2e054 |
IME patterns: quote-flexible report deltas for IME 1.104 serialization
Live validation against session 0fe2c562 (IME 1.104.102, first session on
the repaired pattern set) showed all report-delta patterns at zero: IME
1.104 logs the delta JSON with single quotes ({"EnforcementState":...)
where older versions logged doubled quotes ({""EnforcementState"":...).
Every literal quote in the seven delta patterns (DETECTED-INSTALLED-1,
ENFORCEMENT-SUCCESS, ERROR-DOWNLOAD, ERROR-REPORT, NOT-APPLICABLE-2/3/4)
is now matched as "{1,2} to cover both serializations.
Verified against the session's AppWorkload.log via validate-ime-patterns:
ENFORCEMENT-SUCCESS 12 (was 0), ERROR-REPORT 14 (was 0); no CRITICALs;
the 2026-08-17 repair set confirmed live (ESP-TRACK-STATUS 24,
ESP-APP-REGISTERED 12, CACHE-MISS 11 in the agent's own match log).
Also observed in 1.104, tracked separately (no pattern change possible):
[DO TEL] line removed entirely - the DO download-duration display feeds
on it; needs an agent-side replacement (DO collector enrichment).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
a3089753d3 |
IME patterns: repair drift found by decompiled-source audit (IME 1.97 Dec-25)
Audit vs decompiled IME 1.97.107: ~55/79 patterns verified verbatim, but several were broken or dead against the current V3 pipeline. Fixes: - IME-ESP-TRACK-STATUS: drop trailing anchor (line grew ' with name: <n>.') - HS-COMPLIANCE: tolerate both 'remdiation' typo and corrected wording; new HS-COMPLIANCE-POST for the reworded post-detection line - IME-DETECTION-RESULT: capture DetectedDifferentVersion (ordered first) - IME-ENFORCEMENT-SUCCESS: unpin OldValue (Error/PendingReboot/null paths) - IME-ERROR-REPORT: allow unquoted null OldValue on first write - Disable 11 dead patterns (emitters removed with IME's old V1/V2 pipeline; IME-DETECTED-OLD additionally collided with IME-SKIP-INSTALL-DETECTED) New signals (existing actions, tailed files only): - IME-ESP-APP-REGISTERED + IME-CACHE-MISS as app-name sources (replacing dead IME-UPDATE-NAME-1/2/3) - IME-WINGET-ERROR (WinGet failures were invisible) - IME-EXITCODE-MAPPED-FAILED (normal mapped-exit-code failure path) - IME-STOP-SIGNAL (service stop != OS shutdown) - IME-RESTART-STOP-PROCESSING (reboot-gated processing abort) Backend seed diff now also compares Enabled + Parameters - enable/disable flips and parameter changes previously never propagated to the table. All exact wordings verified against decompiled source; regex smoke-tested via validate-ime-patterns.py (engine=regex, 75/75 compiled, all new patterns fire on synthetic lines with the exact IME format strings). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
65651dd599 |
ANALYZE-ID-004 v1.2.0: pre-logon VPN + Hello-as-symptom self-service guidance
From the 3b7ba31f (succeeded) vs gardner/esa (parked) hybrid session comparison: explanation now covers the cached-credentials/pre-logon-VPN variant and why the Hello wizard never appearing is downstream of the missing PRT; new remediation block for VPN machine tunnels, cloud Kerberos trust, explicit Hello disable, update deferral and pre-provisioning. Links the new docs article. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
daa6df6bab |
rules: evaluate 30 device-phase built-ins at the WhiteGlove seal, 6 also on_event
Every built-in whose evidence exists by the end of pre-provisioning Part 1 now carries evaluateOn [enrollment_end, whiteglove_sealed] — app failures, low disk, memory pressure, unsupported build, WU failures, clock skew, ESP device subcategories, mid-ESP reboots, retry-recovery, cert store, local admins, critical vulnerabilities, firmware CA2023, Secure Boot, AutoLogon, PPKG scans, Office install and hybrid/TPM/MDM known-error codes. Six rules additionally evaluate when their (rare, acute) trigger event arrives: on_event:app_install_failed → APP-002/004/007 + CORR-003, on_event:windows_update_failed → DEV-004, on_event:modern_deployment_error → ENRL-003. Deliberately enrollment-end only: APP-010/ESP-004/ENRL-001 (require enrollment_complete/failed), ENRL-002 (session_timeout), DEV-001 (Hello = user phase), APP-015 (user phase, terminal-precondition design), ID-004 (keeps its own hybrid_login_pending trigger). Minor version bump on all 36 changed rules (seed contract); rule JSONs normalized to 2-space formatting; semantic equality vs HEAD verified (only evaluateOn/version differ). Backend 4157 tests green (APP-013 version pin updated). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
98fd173fb8 |
rules+backend+mcp: rule-verify sweep fixes (dead factor, {{token}} evidence, stats cap, list stringify)
Full verify pass over all 54 built-in rules (validate_rule sweep, 30d/7d
get_rule_stats, test_analyze_rule spot checks). Fixes:
- ANALYZE-APP-007 v1.1.0: declare enrollment_failed as a non-required
condition — the exists-factor referenced an undeclared signal and could
never apply.
- ANALYZE-APP-014 v1.1.0: add non-required event_data conditions for
espCategory/espErrorCode/topCandidatePackage/candidateCount/confidence
so all explanation {{tokens}} resolve instead of rendering literally.
- ANALYZE-ENRL-002 v1.2.0: capture session_timeout.timeoutHours so
{{timeoutHours}} interpolates (verified via dry-run: renders "5").
- RuleEngine TryFlatLookup/ResolveDotPath: flatten IEnumerable data
values to a comma-joined string — List fields (e.g. app_tracking_summary
.pendingNames) stringified to "System.Collections.Generic.List`1[…]",
breaking contains-matching and {{pendingNames}} interpolation.
- GetRuleStatsAsync: raise the 500-row cap to 10000 and log when hit.
The scan walks dates ascending, so the old cap silently dropped the
NEWEST dates of a 30d window — newly added rules (e.g. ANALYZE-SEC-008)
were entirely missing from global stats.
- MCP validate_rule lint: accept event_data_array itemField as a {{token}}
resolver — {{identity}} in ANALYZE-SEC-005/006 was a false positive.
Backend 4157 tests green, MCP 548 green + tsc clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
72136f0273 |
backend+mcp+web: evaluateOn interim triggers for analyze rules
Implements docs/rules/analyze-rule-triggers.md (spec approved today): - AnalyzeRule.EvaluateOn (absent = [enrollment_end], today's behavior bit-for-bit) with the shared AnalyzeRuleTriggers grammar/matching helpers; grammar enforced in tenant rule CRUD and the analyze-rule JSON schema. - RuleEngine: AnalyzeRunContext (Terminal/Interim/Reanalyze), interim rule filter, KO suppression on interim runs, and RuleResult update-semantics replacing the permanent dedupe freeze — interim rows refresh, the terminal pass finalizes or resolves them (ResolvedAt, kept for audit), Reanalyze preserves lifecycle markers. - Notification dedupe moves onto RuleResult.NotifiedAt (one send per session+rule, stamped after a successful send; survives the manual reanalyze rebuild). - Handler: whiteglove_sealed / interim_trigger reasons — notify + SignalR yes, IssuesDetected + rule stats terminal-only (Rule Regression Radar baselines clean). - Ingest: InterimTriggerRegistry (per-tenant 5-min TTL cache, fail-soft) gates the interim_trigger enqueue (batch eventType intersection) and the whiteglove_sealed enqueue on the first genuine WhiteGlove seal. - Storage: EvaluateOnJson on AnalyzeRules; FirstDetectedAt/LastEvaluatedAt/ IsInterim/ResolvedAt/NotifiedAt on RuleResults (nullable — legacy rows read as final). - MCP: three authoring lints (terminal-precondition trap, unknown/high-frequency on_event types, KO-on-interim), authoring guide + baked schema updated; get_session_summary marks interim and excludes resolved findings. - Web: preliminary badge on interim findings, resolved findings behind a toggle, issue counts exclude resolved (analysis section + diagnosis flow). - ANALYZE-ID-004 v1.1.0: first interim-enabled rule (on_event:hybrid_login_pending), threshold raised to 75 so only repeated overdue-login signals fire mid-run. Suites: Backend 4152, MCP 548, Web 762 + tsc + lint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6c6014940e |
rules: hybrid user-affinity + never-settled-app detection, dsregcmd on_event snapshot, evidence interpolation fixes
ANALYZE-ID-004 (hybrid sign-in never establishes Entra user affinity),
ANALYZE-APP-015 (required app pending forever, ESP completion deferred),
ANALYZE-ENRL-002 v1.1 (desktop-reached + apps-done evidence), GATHER-DEVICE-006
(dsregcmd /status triggered by hybrid_login_pending). Engine: list evidence
values now comma-joined for UI/{{token}} interpolation; timeoutHours added to
the evidence whitelist so ANALYZE-ENRL-002's explanation interpolates.
Motivated by esa-logistics sessions 3ac8cb0e/95f71149 (user-phase stuck,
GlobalProtect never settling, placeholder identity persisting across reboots).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
14acfd5c59 |
rules: renumber Secure Boot rule to ANALYZE-SEC-008 — SEC-002 is a retired ID
ANALYZE-SEC-002 was the sunset AutoLogon rule; BuiltInRulesTests pins that the ID never returns (the CI failure on main). Reusing a retired ID would also inherit any historical tenant state keyed to it. Rule content is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9fce499f4c |
rules: ANALYZE-SEC-002 — warn when Secure Boot is disabled
Fires on secureboot_status.uefiSecureBootEnabled=false; legacy BIOS devices (registry key absent, agent sets note field) are skipped via precondition. +20 confidence on physical devices. Validated with MCP validate_rule (0 findings) and test_analyze_rule dry-runs: fired at 90 on a physical SecureBoot:False session, silent on a SecureBoot:True session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e7ea431ccf |
feat(esp): recovery story for terminally reported ESP failures (session 4910a5a5)
DeviceSetup/Apps failed, the user pressed "Try again" ~23 min later, apps recovered to 10/10 and DeviceSetup resolved - yet the advisory_completion window un-defanged the stale failure and failed the live enrollment, because the advisory variant was exempt from all re-arm/rebase guards. Engine fix (DecisionCore): - New facts EspAdvisoryFailureResolvedUtc + EspAdvisoryFailureCategory (state-borne; deadline payload would be lost on re-arm/rebase). - TryResolveAdvisoryOnCategoryRecovery in both ProvisioningComplete handlers: category match sets the fact + emits esp_failure_advisory_resolved. - Guards now (!hasAdvisoryAnchor || advisoryResolved): enforcement-progress re-arm, hello-never-observed promote and the reboot rebase apply again once the failure demonstrably un-happened; unresolved advisories keep the legacy un-defang exactly. - Truthful terminal reason esp_recovered_without_completion_evidence (+ failureRecovered=true, LastFailureTrigger=DeadlineFired so the likely-stuck app promotion stays off). Story events (agent tracker): - esp_failure_retry_detected: failed subcategory leaves "failed" after the terminal fire - consistent with the user pressing "Try again". - esp_failure_recovered: previously failed category reaches success. - EspFailureDetectedEventArgs.LikelyCulpritApps (never-started first, cap 3) rides the EspTerminalFailure payload so esp_failure_advisory / enrollment_failed name the culprit app directly. Surfacing: MCP resource-catalog, web timeline badges (Recovered / User retry pills, culprit chip on advisory), gather-rule autocomplete, ANALYZE-ESP-006 "ESP Failure Recovered After User Retry". Tests: AdvisoryRecoveryTests (13, incl. the 527/611/635/671 field-ordinal replay that no longer fails the session), EspFailureRecoveryStoryTests (5), snapshot-builder guards 18 -> 20. All suites green (DecisionCore 493, V2.Core 2018, Backend 3201, web vitest 589, MCP 449 incl. drift). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
90012d9a66 |
feat(agent): verify Secure Boot CA-2023 state against firmware, not just servicing registry
Customer report: ANALYZE-SEC-001 warned on devices whose firmware already carried the Windows UEFI CA 2023 certificate — the agent only read the SecureBoot\Servicing registry value, which mirrors the Windows Update rollout and lags behind reality (OEM factory image, WinCS, WU not run yet). Agent: new UefiSecureBootCertReader reads the UEFI db/KEK variables natively (GetFirmwareEnvironmentVariableExW; SeSystemEnvironmentPrivilege enabled on the process token and restored — first privilege-adjustment code in the repo). Fail-soft, never throws; legacy BIOS maps to not_uefi. secureboot_status gains per-certificate booleans, uefiFirmwareReadStatus, and the one-sided marker uefiCa2023FirmwareConfirmed (emitted only on positive confirmation, never as false). Rule v3.0.0: not_exists precondition on the marker suppresses the rule when the firmware confirms the certificate; the marker's absence keeps old agents on exact v2 behavior, so the rule is safe to seed before the agent rollout. Firmware-verified-missing raises confidence (+20 db, +10 KEK 2K CA 2023). Verified: full solution build; backend 3201, agent 1988, web 582 tests green; tsc/ajv clean; elevated cross-check on real firmware matches Get-SecureBootUEFI on all seven certificate probes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a97888ebd5 |
fix(agent): neutral aggregated 2800 observations; reboot claim gated on observed reboot
Rework after the first real enrollment (session b2e890c1) exposed three
defects in
|
||
|
|
a654c7309e |
feat(agent): attribute mid-ESP coalesced reboots to MDM policy URIs (second sign-in detection)
Device-assigned MDM policies whose CSP URIs match the OS reboot-required
catalog force a coalesced reboot at the end of ESP DeviceSetup - the user
sees an unexpected restart and a second sign-in before AccountSetup.
Invisible in the Intune console; admins simply lived with it. Now attributed
and actionable (PMPC research).
- MdmRebootPolicyTracker (+host): watches DeviceManagement-Enterprise-
Diagnostics-Provider/Admin EventID 2800, emits one mdm_policy_reboot_required
per reboot-forcing URI (Warning, ImmediateUpload; tolerant EventData-first
URI extraction with regex fallback; fail-soft OMADM RebootRequired probe).
Cross-restart RecordId watermark (mdm-reboot-watermark.json) - mandatory,
the coalesced reboot restarts the agent and the backfill re-reads the log.
- ShellCoreTracker: RebootCoalescing branch on the already-watched 62407
stream -> esp_reboot_coalescing corroboration event; deliberate deviation:
emits on backfill too (live emit races the reboot kill).
- ANALYZE-ESP-005 advisory (warning): names the first URI via {{rebootUri}},
remediation = reassign profiles device -> user groups; +20 confidence on
coalescing corroboration, +10 on >=2 URIs.
- Config: Collectors.MdmRebootPolicyWatcherEnabled (default ON, internal
steering only - agent-native collector, NOT an IAgentAnalyzer, no admin UI),
MdmRebootPolicyBackfillLookbackMinutes=60; ConfigVersion 35.
- Event types deliberately distinct from system_reboot_detected so the
backend RebootCount (exact-string match, 2 sites) stays uninflated.
- Docs: docs/agent/mdm-reboot-coalescing.md (+index/log). Exact 2800/62407
message texts are unverified until E2E VM validation (tolerant matching).
Tests: Agent.V2.Core 1868/1868 (21 new), Functions 3099/3099, MCP 400 passed
(event-types drift), solution build 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
34edadf0db |
docs(rules): document observability limits of the ZTD known-issue rules
The agent is normally installed by an IME platform script AFTER MDM enrollment succeeded, so pre-enrollment failures (enrollment blockers, TPM attestation) are invisible in the normal flow — no enrollment means no agent and no authenticable session. Await-enrollment mode does not help either: the binary only polls for the MDM certificate and cannot authenticate without it. Only bootstrap-token deployments (one pilot customer) can report pre-MDM. - New Observability section in docs/agent/autopilot-ztd-diagnostics.md with a per-rule visibility matrix (normal / bootstrap / await) and the follow-up option of backfilling Autopilot-channel error records - ANALYZE-ENRL-003/004 descriptions state the limitation explicitly so portal users understand why these rules rarely fire Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cd870e639d |
feat(agent+rules): evidence-backed ZTD diagnostics for missing Autopilot profiles
Agent (autopilot_profile_missing evidence package): - ZtdEvidence collector: one-shot all-level query of the ModernDeployment Autopilot event channel (807/809/815/908 errors + 100/153/160/161/163/164 flow events) -> ztdVerdict field (device_not_registered / no_profile_assigned / download_attempted_no_profile_returned / ...); the continuous watcher can't supply this (filters out Info) - Diagnostics\Autopilot registry dump (IsAutopilotDisabled, TenantMatched, CloudAssignedTenantDomain/Id) on every autopilot_profile event, flat evidence fields on the missing event - ztd.dds.microsoft.com reachability probe (official endpoint per the requirements page - several blogs cite ztd.ssd.*, which is wrong); volatile probe fields excluded from the restart-dedup fingerprint; Intune diag-upload endpoint deliberately NOT probed (user decision) Analyze rules (known-issue map from Microsoft Learn): - ANALYZE-ENRL-003: TPM attestation known error codes (0x800705B4, 0x801C03EA, 0x81039001/23/24, 0x80070490) - ANALYZE-ENRL-004: enrollment-blocked known codes (0x80180014, 0x80180018, 0xC1036501, 0x801C03F3, 0x80070774) - ANALYZE-ENRL-005: hybrid-join 0x80004005 timeout with per-version KB fix builds (gated on isHybridJoin via confidence threshold) - ANALYZE-DEV-007: NTP clock skew > 2min (documented TPM-attestation/ESP timeout cause) using existing ntp_time_check offsetSeconds Docs: new OKF reference docs/agent/autopilot-ztd-diagnostics.md (event-ID table, diagnostic registry, endpoints, error-code map, sources + re-check RSS feed), linked from ZtdEvidence/ModernDeploymentTracker code comments, indexed in docs/index.md + agent/index.md + log.md. Tests: 11 ZtdEvidence verdict tests; full V2 suite 1799 green; rule schema validated (ajv draft2020); backend rule tests 123 green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fa03e290b2 |
feat(agent): os_build_changed detection + WU channel blind-spot census (session 7443317c)
Session 7443317c proved a WU-tracker blind spot: an OOBE quality update
installed (OsBuild 26200.8037 -> 26200.8655 across the mid-enrollment reboot)
yet the deployed WindowsUpdateTracker emitted ZERO events - the watcher armed
fine (no collector_degradation), the spool is durable, and two 60-min backfills
covered the install window, so the WindowsUpdateClient channel simply carried
none of the targeted EventIDs 19/20/43/44. The tenant has diagnostics upload
disabled, so the fix makes the feature self-evidencing instead of guessing IDs:
- OsBuildChangeHost: persists CurrentBuild.UBR (Registry64 view) to
AgentState/os-build-state.json and emits os_build_changed (Info, immediate)
when it differs across a restart - deterministic update corroboration that
cannot be lost to timing, watermarks, or unexpected servicing paths. First
run seeds silently; corrupt state re-seeds.
- WindowsUpdateTracker channel census: when the build provably changed AND zero
targeted WU events were emitted this run, emit a one-shot
windows_update_channel_census (Debug) with an unfiltered EventID histogram of
the WindowsUpdateClient AND UpdateOrchestrator channels over the lookback
window (capped at 5000 records, cap surfaced as censusTruncated). The
histogram arrives via normal event upload and tells us which IDs to add to
WindowsUpdateTargetedEventIds via remote config - no redeploy, no diag ZIP.
Gated by CollectorConfiguration.WindowsUpdateChannelCensusEnabled (default
on, model-default pattern, no ConfigVersion bump).
- No new hardcoded EventIDs (per the device-validation rule) - census data
decides what to enable remotely.
- ANALYZE-DEV-006 "OS Build Changed During Enrollment" (info, not KO):
interpolates {{previousBuild}}/{{currentBuild}}, counts the census as
corroboration. Catalogs: Constants.cs + MCP resource-catalog.ts + web
eventTypes.ts (drift-test enforced).
Tests: 6 OsBuildChangeHost (seed/same/changed/unreadable/corrupt/no-dir),
7 census (gating matrix, truncation surfaced, scanner-throw swallowed,
histogram format), 3 ANALYZE-DEV-006. Agent.V2.Core 1754/1754, Backend
2874/2874, MCP 379 (drift green), web tsc + vitest 367/367, sln 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
50295e8304 |
feat(agent): AppX/MSIX failure enrichment in ESP settle window (esp_appx_failure_analysis)
Session 2bc884b6: ESP failed AccountSetup/Apps with 0x80073cf9 while all 8 tracked Win32/WinGet apps succeeded — the failing app was a MSIX/Store app that installs outside the IME Win32 pipeline and is invisible to ImeLogTracker. The session ended as a blackbox failure. When an Apps-subcategory failure arms the 30s settle window, the tracker now queues a one-shot scan of Microsoft-Windows-AppXDeploymentServer/Operational (Critical+Error, session window, 200-record/10s cap), scores candidate packages (exact-HRESULT match > AppX-family error > time proximity, recency bonus), and emits esp_appx_failure_analysis with flat RuleEngine fields plus a capped candidates[] detail list — before enrollment_failed lands in the timeline. Negative results are emitted too (no_appx_candidates points back at Win32). Fire-once per session; scan runs lock-free on the threadpool; settle expiry never waits; failures degrade to collector_degraded. - New: AppxDeploymentFailureScanner (one-shot reader, injectable seam), AppxFailureAnalyzer (pure scoring/verdicts, incl. negative-decimal HRESULT + PackageFullName extraction from message/properties) - ProvisioningStatusTracker: optional scanner/dispatcher ctor seams, MaybeStartAppxFailureScan at settle-arm time - Rule ANALYZE-APP-014 (MSIX/Store app failure during ESP) keyed on verdict=appx_candidate_identified; dist regenerated - MCP resource-catalog + web HRESULT badge + gather-rules eventTypes - Tests: 16 analyzer + 7 trigger; settle-window fixture now injects a fake scanner + sync dispatcher (no real event log in tests) Inert until agent redeploy (rule needs backend redeploy). Post-deploy checks in tasks/todo.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
33618e183d |
feat(wu-oobe): detect & surface Windows Update during OOBE + self-maintaining rule sunset
Windows Update activity during Autopilot OOBE/ESP is a blind spot no other tool (Intune console included) surfaces well — a cumulative update installing mid-OOBE can break enrollment (r/Intune KB5095189). This makes it visible with secured evidence. Agent (net48): - WindowsUpdateTracker + WindowsUpdateWatcherHost: watch Microsoft-Windows-WindowsUpdateClient/Operational (EventIDs 19/20/43/44 = success/failure/install-started/download-started), startup backfill (OOBE updates run before the agent starts), RecordId watermark dedup (restart boundary vs intra-run seen-set so a high-id live event can't hide older backfill events), HRESULT->symbol decode. Emits windows_update_succeeded/_failed/_started with the real event Timestamp. Default-on config knobs; uncertain EventIDs configurable without redeploy. - RegistryCollector: emitOnlyIfExists (suppress positive-assertion events when the key is absent) + Registry64 base view by default (bitness-independent, matches TenantIdResolver) with optional registryView override. Rules: - ANALYZE-DEV-004 (high: WU failed during enrollment, high-insight not KO) + ANALYZE-DEV-005 (info: WU installed). reboot_pending corroboration matches event_data exists==true. - GATHER-DEVICE-005 -> windows_update_reboot_pending (emitOnlyIfExists), GATHER-DEVICE-004 -> windows_update_history (Get-HotFix, opt-in). - 5 EventTypes synced across Constants + MCP resource-catalog + web eventTypes. Self-maintaining rule lifecycle (analyze + gather parity): - Gather rules gain the sunset/orphan-GC that only analyze had (ProcessSunsetGatherRuleAsync + runtime filter + seed/reseed/github paths). - RuleProvenance field (embedded/github, null=embedded back-compat): the embedded catalog only sunsets its own rules; GitHub-reseeded rules ahead of the deployed binary (new ids AND content updates to existing ids) are exempt and reclaimed to embedded once the binary catches up. No orphans, no operator knowledge required. Verified: full solution builds; backend suite 2770 pass, agent suite 1638 pass; rules AJV-valid + combined. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a70785fb42 |
feat(rules): built-in analyze-rule hygiene pass ahead of public docs catalog
- Retire ANALYZE-ESP-003 (duplicate of ESP-001) and ANALYZE-CORR-002 (duplicate of APP-004) via the established sunset path; ESP-001 gains a >60min factor, APP-004 absorbs the co-occurrence story with working confidence factors - CORR-003: download evidence (errorPatternId) is now a required condition — no more firing on proxy + any unrelated app failure - DEV-002 v2: 'sustained' semantics — new event_count value filter (filterField/filterOperator/filterValue in schema, Shared model, engine, web types) requires >=3 snapshots above 90% memory instead of one transient spike - DEV-003 v2: build-cutoff-only condition (<=22621) now also covers Windows 10; LTSC caveat + maintenance note (raise to 22631 after 2026-11-10) - SEC-001 v2: rewritten for the post-June-2026 reality — certificates HAVE expired, active remediation required (OEM firmware hint added) - SEC-005/006 v2.1: allow-list extended with OEM factory-preload families (Intel PPM, AMD Power, Lenovo ThinkPad power/battery, Surface/carrier), telemetry-curated from 1780 fired sessions; imaging leftovers (USMT.PPKG, InstallOrder.ini) and generic names deliberately still alert - APP-001: suppressByEvent on app_install_completed; APP-002: exact MSI exit codes instead of bare 16xx; APP-006 reframed as 'App Install Retry Loop'; ID-001: title/logic mismatch fixed + GATHER-ID-002 dependency documented - Fix all 13 dead confidence factors (signal-name mismatches / unsupported syntax) by rewiring or removal; version bumps everywhere so seeding propagates the updates - New RuleEngineEventCountFilterTests + SEC-005 OEM allow-list coverage Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
44203423ec |
feat(rules): ANALYZE-ESP-004 soft-failure for ESP-timeout + Continue-anyway
Devices that hit the ESP terminal timeout in DeviceSetup on a profile that allows "Continue anyway" are recorded as Failed even though the user most likely dismissed the error and reached the desktop. The existing advisory- defang only covers AccountSetup failures, so DeviceSetup timeouts (e.g. a slow blocking app missing the 30-min wall) show up as hard failures. Add a built-in, enabled-by-default analyze rule (severity: warning) keyed on enrollment_failed.mayHaveContinuedAnyway==true. It surfaces the soft failure and points at the root cause (slow blocking app / ESP timeout / detection mismatch / masked certificate failure) without changing session lifetime or the terminal status. No agent change required; ships via EmbeddedResource or ReseedFromGitHub. Tests: RuleEngineEspSoftFailureTests (fires on the continue-anyway shape, stays silent on hard failures and when the flag is absent). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
474af7f78c |
fix(agent): ESP-Apps failure correlation + platform-script stdout/exit contamination
Two V2 IME fixes, verified against client logs of sessions fc48c71a / 6b4993e5. Bug 1 — on an ESP terminal failure of the DeviceSetup Apps subcategory the only device app was stuck mid-install (RealmJoin stuck Downloading 0%) and IME logged no per-app error, so app_tracking_summary showed 0 failed and the failure was visible only via the opaque "Apps (Error)" registry string. New esp_apps_failure_correlation event names the in-flight (IsActive: InProgress/ Downloading/Installing) device app(s) as the likely cause WITHOUT mutating state (no fabricated app_install_failed) and only on the Apps subcategory. Bug 2 — AgentExecutor.exe hosts both platform and proactive-remediation scripts interleaved in the shared AgentExecutor.log; the policyId-less PS-AGENT-EXITCODE / PS-AGENT-OUTPUT lines from a remediation invocation bled into the last-started platform script via the sticky pointer (c3e0124c emitted result=Failed but with exit 0 + the Teams remediation's "[Compliant] No Classic Teams found" stdout). New PS-AGENT-INVOCATION pattern (resetPlatformScriptContext) clears the platform line-capture pointer at every "ExecutorLog AgentExecutor gets invoked" boundary, so a remediation invocation cannot capture into a platform slot; the platform result stays keyed by policyId via PS-SCRIPT-RESULT. Agent-only, V2, inert until redeploy; no ConfigVersion bump. New event type added to the MCP resource catalog (dual-sync). Green: V2.Core 1522, DecisionCore 400, Backend BuiltInRules 8, MCP vitest 252. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
cf8fadfc17 |
fix(rules): drop AutoLogon false-positive on normal Autopilot enrollments
ANALYZE-SEC-002 fired Warning (confidence 90) on every normal user-driven Entra-join enrollment. During ESP, Windows writes AutoAdminLogon=1 + DefaultUserName + AutoLogonSID + no DefaultPassword for the enrolling user — the exact fingerprint the sysinternals_autologon_suspected heuristic targeted (enabled && user && !password). AutoLogon-enabled is registry-indistinguishable from a Sysinternals/kiosk setup during Autopilot, so the heuristic and SEC-002 could never tell a benign enrollment from a real one. Declare AutoLogon only when a plaintext password is actually present: - delete ANALYZE-SEC-002 (broad AutoLogon-active warning) - remove the non-discriminating sysinternals heuristic from AutoLogonAnalyzer (computation + finding label + checks.sysinternals_autologon_suspected) - strip the sysinternals condition+factor from SEC-004 (kiosk allow-list, still a disabled opt-in template) and refresh its now-stale SEC-002 guidance - ANALYZE-SEC-003 (plaintext DefaultPassword -> high) is now the sole default-on AutoLogon grade; the autologon_analysis event still carries all raw facts at Info Tests: assert SEC-002 absent + no rule references the sysinternals signal; add analyzer regression guard for enabled+user+no-password. V2-only, no ConfigVersion bump. Build 0 err, AutoLogon 19/19, BuiltInRulesTests 8/8, web tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dbfdc9b239 |
feat(agent): event-driven Office (M365 Apps C2R) install detector + DO telemetry
Surfaces the real Microsoft 365 Apps Click-to-Run install lifecycle that the Intune "integrated" app hides (IME reports done in 1-2 min while C2R keeps streaming in the background). V2-only. Event-driven, no idle polling: - OfficeProcessWatcher: WMI Win32_ProcessStartTrace push on OfficeC2RClient.exe (+ startup probe) for start; Process.Exited for stop, debounced by a settle window (OfficeInstallSettleSeconds, default 10s) so short worker gaps in one install don't trigger a premature office_install_failed. - RegistryChangeWatcher: RegNotifyChangeKeyValue push on ...\Office\ClickToRun (Configuration value + Scenario subkey churn); bootstrap fallback watches the nearest existing ancestor non-recursive + name-only until ClickToRun appears. - OfficeInstallDetector: pure state machine (Idle->Active->Terminal), emits office_install_started/progress/completed/failed (progress on real change only, no heartbeat). Error code only from non-zero numeric/hex Scenario values. Office Delivery Optimization: - DeliveryOptimizationCollector extended with a 2nd wake source (Office active), classifies non-IME Office-CDN jobs and folds aggregated DO bytes/peers/real download-% into the office_install_* events (not emitted as download_progress to avoid a phantom app in the backend AppInstallSummary). Plumbing: office_install_* event-type constants, EnableOfficeInstallDetector + OfficeInstallSettleSeconds config (ConfigVersion 28->29), MCP + web event-type catalogs, ANALYZE-OFFICE-001 (office_install_failed -> Warning). 22 detector unit tests; full V2 suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |