Skip to content

feat(intune): implement Windows Autopilot evidence parser outside ESP (issue #362) - #450

Merged
adamgell merged 4 commits into
mainfrom
copilot/implement-autopilot-evidence-parser
Aug 8, 2026
Merged

feat(intune): implement Windows Autopilot evidence parser outside ESP (issue #362)#450
adamgell merged 4 commits into
mainfrom
copilot/implement-autopilot-evidence-parser

Conversation

Copilot AI commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Implements cmtraceopen_parser::intune::enrollment::windows::autopilot — a pure Rust, wasm32-compatible module that reduces Windows Autopilot device identity/profile/OOBE evidence into an immutable typed snapshot. ESP remains a sibling reducer with its own contract; the only thing crossing the boundary is an explicit shared key.

New modules under crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/

  • sources.rs — Input contract (AutopilotBundleInput, AutopilotSourceInput); explicit schema detection that refuses untagged/unknown-version documents; Windows build and Autopilot schema version gating on terminal semantics
  • normalize.rs — Maps the 16 Microsoft-documented Autopilot event IDs to typed AutopilotSignal variants; anything outside that table classifies as Unclassified and is retained but never promoted to a terminal conclusion
  • reducer.rs — Pure fold: evidence bundle → immutable AutopilotSnapshot; computes outcome, phase, ESP linkage, conflicts, coverage, and next-artifact requests in a single pass
  • rules.rs — Evidence-backed findings only; each finding cites an exact observation or coverage gap and names the next smallest artifact
  • models.rs — Typed snapshot contract (12-variant AutopilotOutcome, 7-stage AutopilotPhase, AutopilotEspLinkage, device identity, profile state, OOBE state)
  • redaction.rs — Deterministic privacy projection: device/user identity masked to stable tokens, tenant object identifiers (profile ID, enrollment ID, correlation keys) preserved for cross-tool correlation

Fixture matrix and tests

15 synthetic scenarios covering all cases required by the issue spec, validated by 25 integration tests in tests/intune_windows_autopilot.rs.

Audit fixes applied over the recovery branch baseline

The preserved codex/recovery-intune-362-autopilot branch had a passing test suite but eight confirmed semantic defects. All are addressed here:

Defect Fix
NormalizedWindowsEvent / AutopilotObservation missing event version event_version: Option<u32> added to both
reduce_esp_linkage emitting TimeOnlyCandidate unconditionally when no explicit key matched Now requires time_basis == UTC and actual normalized-UTC timestamp overlap; otherwise NotObserved
Disjoint keys silently binding multiple ESP sessions as high-confidence Linked matched_sessions.len() > 1Conflicting before the Linked return
Redaction token namespaces differing between identity fields, named data, and correlation keys (breaking cross-field equality) All token minting unified to VALUE_KIND = "redacted"
is_token() treating any [foo:bar] string as already-masked Only [redacted:<16 lowercase hex chars>] recognised as a token
Source input discarding provenance metadata source_kind_tag, rotation_index, fragment_index added to AutopilotSourceInput
Malformed/access-denied observations driving terminal outcomes is_assessable() guard (access_state == Available && parse_state == Parsed) applied to all outcome-driving iteration
cargo fmt --check --all failure across the workspace cargo fmt --all applied

The invalid-timezone fixture's espLinkState is corrected from timeOnlyCandidate to notObserved to match the fixed reduction; its finding set and assertions are updated accordingly.

Summary by CodeRabbit

  • New Features

    • Added Windows Autopilot enrollment analysis, including profile, device identity, OOBE, registration, handoff, and ESP session details.
    • Added evidence-based enrollment outcomes, confidence levels, findings, conflicts, and recommended follow-up checks.
    • Added support for source validation, schema detection, timezone assessment, unknown events, and incomplete evidence handling.
    • Added deterministic privacy redaction for sensitive identifiers and text.
  • Bug Fixes

    • Improved event processing compatibility by tracking event versions.
  • Tests

    • Added comprehensive coverage for successful, incomplete, conflicting, malformed, unsupported, and failure scenarios.

Add cmtraceopen_parser::intune::enrollment::windows::autopilot with:
- models.rs: typed snapshot contract (AutopilotSnapshot, outcome, phase, ESPLinkage)
- sources.rs: input contract, explicit schema detection, capture metadata validation
- normalize.rs: classification of validated Autopilot event IDs to typed signals
- reducer.rs: pure fold from evidence → immutable snapshot with all audit fixes
- rules.rs: evidence-backed findings with next-artifact pointers
- redaction.rs: deterministic privacy projection with unified redaction token namespace
- 15-scenario fixture matrix (all required by issue spec)
- Integration test suite: intune_windows_autopilot.rs (25 tests)

Audit fixes applied over recovery branch baseline:
- Add event_version field to NormalizedWindowsEvent and AutopilotObservation
- Add source_kind_tag/rotation_index/fragment_index to AutopilotSourceInput
- Gate outcome reduction on is_assessable (access_state==Available, parse_state==Parsed)
- reduce_esp_linkage: only emit TimeOnlyCandidate with UTC time basis + actual overlap
- Disjoint keys matching >1 ESP session → Conflicting, not high-confidence Linked
- Unify redaction token namespace to single VALUE_KIND=redacted everywhere
- Tighten is_token() to only accept [redacted:<16hex>] format produced by stable_token

Passes: cargo test --locked -p cmtraceopen-parser (938 total)
        cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings
        cargo check --locked -p cmtraceopen-parser --target wasm32-unknown-unknown
        cargo fmt --check --all
Copilot AI changed the title [WIP] Implement Autopilot evidence parsing for Windows enrollment feat(intune): implement Windows Autopilot evidence parser outside ESP (issue #362) Aug 3, 2026
Copilot AI requested a review from adamgell August 3, 2026 06:03
@adamgell
adamgell marked this pull request as ready for review August 5, 2026 03:14
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR implements the Windows Autopilot parser. It adds public contracts, event normalization, evidence reduction, findings, redaction, ESP correlation, and a 15-scenario fixture test suite.

Changes

Autopilot contracts and snapshot model

Layer / File(s) Summary
Public contracts and state model
crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/*, crates/cmtraceopen-parser/src/intune/normalized.rs
Defines versioned source documents, capture states, normalized events, immutable snapshots, identity/profile/OOBE state, ESP linkage, conflicts, findings, and evidence coverage.
Event normalization
crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
Validates provider and channel, classifies event IDs, and extracts HRESULTs, profile states, OOBE settings, and policy values while preserving unknown values.
Evidence reduction and findings
crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs, crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rs
Builds deterministic snapshots from supplied artifacts, reduces identity and profile state, correlates ESP sessions, tracks coverage and timestamp limits, derives outcomes, and emits evidence-backed findings.
Privacy projection
crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rs
Adds deterministic, idempotent redaction for sensitive identifiers, text, observations, findings, conflicts, linkage, and evidence requests.
Scenario validation
crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs, crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/*
Adds 15 synthetic scenarios covering success, failures, conflicts, incomplete or unsupported evidence, timezone handling, ESP correlation, and identity redaction.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AutopilotBundleInput
  participant normalize
  participant reduce_autopilot_bundle
  participant derive_findings
  participant redacted_export_projection
  AutopilotBundleInput->>normalize: normalized events and source documents
  normalize->>reduce_autopilot_bundle: classified signals and extracted values
  reduce_autopilot_bundle->>derive_findings: immutable snapshot evidence and coverage
  derive_findings->>reduce_autopilot_bundle: findings and evidence requests
  reduce_autopilot_bundle->>redacted_export_projection: completed AutopilotSnapshot
  redacted_export_projection->>AutopilotBundleInput: redacted snapshot projection
Loading

Possibly related PRs

  • adamgell/cmtraceopen#388: Introduces the Autopilot module skeleton and shared NormalizedWindowsEvent contract used by this implementation.

Suggested labels: enhancement, intune, parser, test, feature

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title follows Conventional Commits and clearly describes the Windows Autopilot parser implementation in the Intune scope.
Linked Issues check ✅ Passed The changes implement the requested Autopilot contracts, reduction, findings, redaction, ESP correlation, and 15-scenario fixture matrix for issue [#362].
Out of Scope Changes check ✅ Passed The changes remain within Autopilot parser scope; event-version updates and related fixture initializers support the new normalized event contract.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch copilot/implement-autopilot-evidence-parser

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.6)
crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.json

File contains syntax errors that prevent linting: Line 1: unexpected character <; Line 1: unexpected character !; Line 1: Minus must be followed by a digit; Line 1: Minus must be followed by a digit; Line 1: String values must be double quoted.; Line 1: String values must be double quoted.; Line 1: End of file expected; Line 1: Minus must be followed by a digit; Line 1: Minus must be followed by a digit; Line 1: Minus must be followed by a digit; Line 1: unexpected character >; Line 2: unexpected character <; Line 2: unexpected character =; Line 2: End of file expected; Line 2: unexpected character >; Line 3: unexpected character <; Line 3: String values must be double quoted.; Line 3: unexpected character >; Line 4: unexpected character <; Line 4: String values must be double quoted.; Line 4: String values must be double quoted.; Line 4: unexpected character =; Line 4: Missing closing quote


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rs`:
- Around line 112-122: Correct the doc comment above opaque_blob_re to state the
actual 40-character minimum enforced by the regex pattern `{40,}`. Keep the
existing explanation intact, updating only the inaccurate bound and any directly
dependent wording.
- Around line 204-208: Replace the direct stable_token calls in the matched_keys
handling and the corresponding sites around the projection logic with
mask_value, reusing its token check and trim/lowercase normalization. Update all
four affected masking paths so equivalent entraDeviceId values correlate despite
case or surrounding whitespace, and add a fixture scenario where the same ID
appears with mixed casing across two artifacts.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs`:
- Around line 639-649: Align the conflict-handling comment and implementation
around single_value with detect_conflicts: either make detect_conflicts use the
same shared key list as single_value, or narrow the comment to state that only
profileId, serialNumber, and entraDeviceId conflicts are reported separately. Do
not imply that disagreements for the other identity and enrollment keys are
reported.
- Around line 1359-1366: Update the outcome match in the profile.applied and
handoff.esp_observed branch to handle AutopilotEspLinkState::Conflicting
explicitly as ContradictoryEvidence, alongside the existing EvidenceMissing
case. Keep Completed only for non-conflicting linkage states, so the
matched-sessions conflict path cannot report successful completion.
- Around line 743-792: Update the observation iteration in reduce_profile to
process only assessable observations via the existing is_assessable filtering
mechanism, matching reduce_identity, reduce_oobe, has_signal, and
distinct_values. Ensure unreadable or unparsed records cannot update retrieved,
applied, candidate, evidence, or error state, while preserving the current
handling for assessable observations.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rs`:
- Around line 57-62: Update the unvalidated-schema handling around
build_unvalidated so captures with a declared autopilot_schema_version but no
windows_build are also retained for explanation when the outcome is
UnknownSchema. Widen the summary logic to identify whichever of windows_build or
autopilot_schema_version was declared, and rename the remaining
build_unvalidated reference near the terminal-rule handling to reflect both
validation inputs.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rs`:
- Around line 355-360: The windows_zone_re pattern is too permissive and
classifies placeholder metadata as declared time zones. Update windows_zone_re
to require Windows zone names to end with the literal “Time”, while preserving
utc_offset_re handling for UTC and offset forms. Extend the existing
classify_timezone test with the specified invalid placeholder cases, including
suffix-like junk.

In `@crates/cmtraceopen-parser/src/intune/normalized.rs`:
- Line 60: Update the externally constructible NormalizedWindowsEvent API by
documenting event_version with field-level Rustdoc and releasing this added
field under the project’s designated breaking-version process. Preserve the
Option<u32> type and existing field behavior.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/autopilot-channel/current/autopilot-events.json`:
- Line 1: Update the fixture’s _comment metadata to describe the
completed-without-ESP-bundle scenario accurately, removing the “happy path”
wording and distinguishing it from user-driven success through ESP handoff.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.json`:
- Around line 1-4: Replace the XML fragment in the malformed Autopilot fixture
with a valid JSON object matching the parser’s document contract, while
preserving a truncated or malformed Autopilot report section for the targeted
parsing failure. Ensure detect_document can deserialize and dispatch the fixture
to AutopilotReportDocument instead of rejecting it as non-JSON.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json`:
- Line 41: The malformed-report golden output currently depends on unstable
serde_json parser text. Update the classification and summary path around
Malformed.detail so malformed JSON uses a stable reason, or exclude the raw
parser detail from the asserted summary, then adjust the expected summary to
match the stable output.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/expected.json`:
- Around line 94-98: Update the profile application state logic so the `applied`
value used by `profileApplied` is set only by explicit application-success
signals, not by event 153’s `ProfileState_Available` state. Then update the
fixture’s expected terminal fields, including `profileApplied` and `outcome`, to
reflect the absence of application evidence while preserving the existing phase
and retrieval values.

In `@crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs`:
- Around line 626-647: Prevent concurrent golden-file access in
update_findings_golden by marking the test #[ignore], and update its
documentation to instruct maintainers to run regeneration via cargo test --
--ignored with UPDATE_AUTOPILOT_FINDINGS set. Change write_json to serialize and
write through a temporary file in the same directory, then rename it over the
target so readers never observe truncation or partial contents.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 25ecc2f7-32ad-4244-a74f-c24dda57df70

📥 Commits

Reviewing files that changed from the base of the PR and between 47467d4 and ad9a251.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock, !Cargo.lock
📒 Files selected for processing (112)
  • crates/cmtraceopen-parser/src/collector/profile.rs
  • crates/cmtraceopen-parser/src/esp/redaction.rs
  • crates/cmtraceopen-parser/src/esp/reducer.rs
  • crates/cmtraceopen-parser/src/esp/timeline.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rs
  • crates/cmtraceopen-parser/src/intune/normalized.rs
  • crates/cmtraceopen-parser/src/intune/portal/windows/company_portal/package_state/mod.rs
  • crates/cmtraceopen-parser/tests/esp_diagnostics.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/self-deploying-contract/current/self-deploying-contract.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/manifest.json
  • crates/cmtraceopen-parser/tests/intune_skeleton_contract.rs
  • crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs
  • crates/cmtraceopen-parser/tests/support/mod.rs
  • src-tauri/src/commands/elevation.rs
  • src-tauri/src/commands/file_ops.rs
  • src-tauri/src/commands/jamf.rs
  • src-tauri/src/commands/mod.rs
  • src-tauri/src/commands/recent_entries.rs
  • src-tauri/src/commands/system_preferences.rs
  • src-tauri/src/elevation/mod.rs
  • src-tauri/src/error.rs
  • src-tauri/src/esp/process.rs
  • src-tauri/src/esp/registry.rs
  • src-tauri/src/esp/system.rs
  • src-tauri/src/graph_api/esp.rs
  • src-tauri/src/graph_api/models.rs
  • src-tauri/src/intune/evtx_parser.rs
  • src-tauri/src/jamf/connect.rs
  • src-tauri/src/jamf/detect.rs
  • src-tauri/src/jamf/mod.rs
  • src-tauri/src/jamf/models.rs
  • src-tauri/src/jamf/paths.rs
  • src-tauri/src/jamf/policy_log.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/macos_diag/unified_log.rs
  • src-tauri/src/menu.rs
  • src-tauri/src/sysmon/evtx_parser.rs
  • src-tauri/tests/esp_diagnostics_sources.rs
  • src-tauri/tests/jamf_environment.rs
  • src-tauri/tests/jamf_ipc_contract.rs
  • src-tauri/tests/jamf_known_sources.rs
  • src-tauri/tests/jamf_parser_robustness.rs
  • src-tauri/tests/jamf_policy_log_parsing.rs
  • src-tauri/tests/jamf_real_fixtures.rs
  • src-tauri/tests/jamf_self_service_log_parsing.rs

Comment on lines +112 to +122
/// A hardware hash or similar long opaque blob embedded in free text.
///
/// Bounded at 32 characters so a GUID (32 hex digits plus dashes, matched in
/// runs of at most 12) and an eight-digit HRESULT are both left readable; those
/// are diagnostic grammar, not identity.
fn opaque_blob_re() -> &'static Regex {
static CELL: OnceLock<Regex> = OnceLock::new();
CELL.get_or_init(|| {
Regex::new(r"\b[A-Za-z0-9+/=]{40,}\b").expect("opaque blob regex must compile")
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The doc comment states the wrong bound.

The comment says "Bounded at 32 characters". The pattern uses {40,}. The reasoning that follows depends on the real bound, so a reader tuning this threshold will trust the wrong number.

📝 Proposed doc fix
-/// Bounded at 32 characters so a GUID (32 hex digits plus dashes, matched in
-/// runs of at most 12) and an eight-digit HRESULT are both left readable; those
-/// are diagnostic grammar, not identity.
+/// Bounded at 40 characters so a GUID (32 hex digits plus dashes, matched in
+/// runs of at most 12) and an eight-digit HRESULT are both left readable; those
+/// are diagnostic grammar, not identity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rs`
around lines 112 - 122, Correct the doc comment above opaque_blob_re to state
the actual 40-character minimum enforced by the regex pattern `{40,}`. Keep the
existing explanation intact, updating only the inaccurate bound and any directly
dependent wording.

Comment on lines +204 to +208
for key in &mut projected.esp_linkage.matched_keys {
if !is_token(&key.value) {
key.value = stable_token(VALUE_KIND, &key.value);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

matched_keys masking skips the normalization that makes correlation work.

Line 206 calls stable_token(VALUE_KIND, &key.value) on the raw value. mask_value (line 85) hashes value.trim().to_ascii_lowercase(). The module doc at lines 29 to 31 promises that every whole-value mask is computed over the trimmed, lowercased value.

The result: identity.entra_device_id and the entraDeviceId correlation key mask to different tokens whenever the two records differ in case or surrounding space. That destroys the exact link this projection exists to preserve.

The same bypass exists at line 234 and at lines 255 and 262. All four sites should call mask_value, which already performs the is_token check.

The current fixtures use one lowercase GUID spelling in every artifact, so the suite cannot catch this. Add a scenario where the same entraDeviceId arrives in mixed case from two artifacts.

🐛 Proposed fix, all four sites
     for key in &mut projected.esp_linkage.matched_keys {
-        if !is_token(&key.value) {
-            key.value = stable_token(VALUE_KIND, &key.value);
-        }
+        key.value = mask_value(&key.value);
     }
         if SENSITIVE_VALUE_KEYS
             .iter()
             .any(|key| key.eq_ignore_ascii_case(&value.name))
         {
-            if !is_token(&value.value) {
-                value.value = stable_token(VALUE_KIND, &value.value);
-            }
+            value.value = mask_value(&value.value);
         } else {
     match value.split_once('=') {
         Some((name, raw))
             if SENSITIVE_VALUE_KEYS
                 .iter()
                 .any(|key| key.eq_ignore_ascii_case(name)) =>
         {
-            if is_token(raw) {
-                value.to_owned()
-            } else {
-                format!("{name}={}", stable_token(VALUE_KIND, raw))
-            }
+            format!("{name}={}", mask_value(raw))
         }
-        _ => {
-            if is_token(value) {
-                value.to_owned()
-            } else {
-                stable_token(VALUE_KIND, value)
-            }
-        }
+        _ => mask_value(value),
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for key in &mut projected.esp_linkage.matched_keys {
if !is_token(&key.value) {
key.value = stable_token(VALUE_KIND, &key.value);
}
}
for key in &mut projected.esp_linkage.matched_keys {
key.value = mask_value(&key.value);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rs`
around lines 204 - 208, Replace the direct stable_token calls in the
matched_keys handling and the corresponding sites around the projection logic
with mask_value, reusing its token check and trim/lowercase normalization.
Update all four affected masking paths so equivalent entraDeviceId values
correlate despite case or surrounding whitespace, and add a fixture scenario
where the same ID appears with mixed casing across two artifacts.

Comment on lines +639 to +649
fn single_value(observations: &[AutopilotObservation], key: &str) -> Option<String> {
let values = distinct_values(observations, key);
let mut keys = values.into_keys();
let first = keys.next()?;
// More than one distinct value is a conflict, reported separately. Picking
// one here would hide it.
if keys.next().is_some() {
return None;
}
Some(first)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The comment overstates conflict coverage.

single_value drops the value for all nine IDENTITY_KEYS plus profileId, profileName, deploymentProfileType, enrollmentId, and correlationId. detect_conflicts only reports three of them: profileId, serialNumber, and entraDeviceId. For tenantId, managedDeviceId, enrollmentId, correlationId, and the rest, a disagreement silently becomes None and is never "reported separately".

Either drive detect_conflicts from a shared key list so the two stay aligned, or narrow the comment to name the three keys it actually covers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs`
around lines 639 - 649, Align the conflict-handling comment and implementation
around single_value with detect_conflicts: either make detect_conflicts use the
same shared key list as single_value, or narrow the comment to state that only
profileId, serialNumber, and entraDeviceId conflicts are reported separately. Do
not imply that disagreements for the other identity and enrollment keys are
reported.

Comment on lines +743 to +792
for observation in observations {
match observation.signal {
AutopilotSignal::ProfileAcquisitionStarted
| AutopilotSignal::ProfilePolicyNotFound
| AutopilotSignal::NetworkAvailableForDownload => {
evidence.push(observation.evidence_ref());
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Pending);
}
AutopilotSignal::ProfileRetrieveSucceeded
| AutopilotSignal::ProfileSettingsRetrieved => {
evidence.push(observation.evidence_ref());
retrieved = true;
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
AutopilotSignal::ProfileStateChanged => {
evidence.push(observation.evidence_ref());
let token = extract_profile_state(observation.message.as_deref());
if matches!(
token,
Some(AutopilotProfileStateToken::Available)
| Some(AutopilotProfileStateToken::Provisioned)
) {
applied = true;
candidate =
raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
if token.is_some() {
last_state_token = token;
}
}
AutopilotSignal::DeviceAlreadyProvisioned => {
evidence.push(observation.evidence_ref());
applied = true;
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
AutopilotSignal::NoAssignedProfile => {
evidence.push(observation.evidence_ref());
candidate = AutopilotProfileCandidateState::NoneAssigned;
}
AutopilotSignal::AssignedProfileMissing => {
evidence.push(observation.evidence_ref());
candidate = AutopilotProfileCandidateState::AssignedButMissing;
}
AutopilotSignal::ProfileApplicationFailed => {
evidence.push(observation.evidence_ref());
error = error.or_else(|| observation.error.clone());
}
_ => {}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

reduce_profile skips the assessability gate, so unreadable records can prove success.

Every other reduction path filters through is_assessable: reduce_identity uses signal_observations (line 686), reduce_oobe uses it (line 868), has_signal uses it (line 602), and distinct_values uses it (line 623). This loop iterates observations directly.

The result is an asymmetry that biases toward success. An observation with access_state != Available or parse_state != Parsed can set retrieved = true, applied = true, and candidate_state = Available. Those feed reduce_phase and the profile.applied && handoff.esp_observed branch at line 1359, which returns Completed. The matching failure branch at line 1353 calls has_signal, which is filtered, so an unassessable failure record is discarded while an unassessable success record is honored.

🐛 Proposed fix
-    for observation in observations {
+    for observation in observations.iter().filter(|obs| is_assessable(obs)) {
         match observation.signal {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for observation in observations {
match observation.signal {
AutopilotSignal::ProfileAcquisitionStarted
| AutopilotSignal::ProfilePolicyNotFound
| AutopilotSignal::NetworkAvailableForDownload => {
evidence.push(observation.evidence_ref());
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Pending);
}
AutopilotSignal::ProfileRetrieveSucceeded
| AutopilotSignal::ProfileSettingsRetrieved => {
evidence.push(observation.evidence_ref());
retrieved = true;
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
AutopilotSignal::ProfileStateChanged => {
evidence.push(observation.evidence_ref());
let token = extract_profile_state(observation.message.as_deref());
if matches!(
token,
Some(AutopilotProfileStateToken::Available)
| Some(AutopilotProfileStateToken::Provisioned)
) {
applied = true;
candidate =
raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
if token.is_some() {
last_state_token = token;
}
}
AutopilotSignal::DeviceAlreadyProvisioned => {
evidence.push(observation.evidence_ref());
applied = true;
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
AutopilotSignal::NoAssignedProfile => {
evidence.push(observation.evidence_ref());
candidate = AutopilotProfileCandidateState::NoneAssigned;
}
AutopilotSignal::AssignedProfileMissing => {
evidence.push(observation.evidence_ref());
candidate = AutopilotProfileCandidateState::AssignedButMissing;
}
AutopilotSignal::ProfileApplicationFailed => {
evidence.push(observation.evidence_ref());
error = error.or_else(|| observation.error.clone());
}
_ => {}
}
}
for observation in observations.iter().filter(|obs| is_assessable(obs)) {
match observation.signal {
AutopilotSignal::ProfileAcquisitionStarted
| AutopilotSignal::ProfilePolicyNotFound
| AutopilotSignal::NetworkAvailableForDownload => {
evidence.push(observation.evidence_ref());
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Pending);
}
AutopilotSignal::ProfileRetrieveSucceeded
| AutopilotSignal::ProfileSettingsRetrieved => {
evidence.push(observation.evidence_ref());
retrieved = true;
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
AutopilotSignal::ProfileStateChanged => {
evidence.push(observation.evidence_ref());
let token = extract_profile_state(observation.message.as_deref());
if matches!(
token,
Some(AutopilotProfileStateToken::Available)
| Some(AutopilotProfileStateToken::Provisioned)
) {
applied = true;
candidate =
raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
if token.is_some() {
last_state_token = token;
}
}
AutopilotSignal::DeviceAlreadyProvisioned => {
evidence.push(observation.evidence_ref());
applied = true;
candidate = raise_candidate(candidate, AutopilotProfileCandidateState::Available);
}
AutopilotSignal::NoAssignedProfile => {
evidence.push(observation.evidence_ref());
candidate = AutopilotProfileCandidateState::NoneAssigned;
}
AutopilotSignal::AssignedProfileMissing => {
evidence.push(observation.evidence_ref());
candidate = AutopilotProfileCandidateState::AssignedButMissing;
}
AutopilotSignal::ProfileApplicationFailed => {
evidence.push(observation.evidence_ref());
error = error.or_else(|| observation.error.clone());
}
_ => {}
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs`
around lines 743 - 792, Update the observation iteration in reduce_profile to
process only assessable observations via the existing is_assessable filtering
mechanism, matching reduce_identity, reduce_oobe, has_signal, and
distinct_values. Ensure unreadable or unparsed records cannot update retrieved,
applied, candidate, evidence, or error state, while preserving the current
handling for assessable observations.

Comment on lines +1359 to +1366
if profile.applied && handoff.esp_observed {
return match esp_linkage.state {
AutopilotEspLinkState::EvidenceMissing => {
AutopilotOutcome::HandoffReachedEspEvidenceMissing
}
_ => AutopilotOutcome::Completed,
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

A Conflicting ESP linkage can still report Completed.

reduce_esp_linkage has two Conflicting paths. The first (line 1132) is reached through an AutopilotConflictKind::EspSessionIdentifier entry in conflicts, so reduce_outcome line 1330 already returns ContradictoryEvidence. The second (line 1212, matched_sessions.len() > 1) records no AutopilotConflict. When only that path fires, conflicts is empty, this branch falls into the _ arm, and the outcome becomes Completed. push_completed then emits "The local Autopilot phase completed and handed off" while the snapshot itself says the ESP session identity is ambiguous.

Match the state explicitly instead of relying on conflicts.

🐛 Proposed fix
     if profile.applied && handoff.esp_observed {
         return match esp_linkage.state {
             AutopilotEspLinkState::EvidenceMissing => {
                 AutopilotOutcome::HandoffReachedEspEvidenceMissing
             }
+            AutopilotEspLinkState::Conflicting => AutopilotOutcome::ContradictoryEvidence,
             _ => AutopilotOutcome::Completed,
         };
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if profile.applied && handoff.esp_observed {
return match esp_linkage.state {
AutopilotEspLinkState::EvidenceMissing => {
AutopilotOutcome::HandoffReachedEspEvidenceMissing
}
_ => AutopilotOutcome::Completed,
};
}
if profile.applied && handoff.esp_observed {
return match esp_linkage.state {
AutopilotEspLinkState::EvidenceMissing => {
AutopilotOutcome::HandoffReachedEspEvidenceMissing
}
AutopilotEspLinkState::Conflicting => AutopilotOutcome::ContradictoryEvidence,
_ => AutopilotOutcome::Completed,
};
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs`
around lines 1359 - 1366, Update the outcome match in the profile.applied and
handoff.esp_observed branch to handle AutopilotEspLinkState::Conflicting
explicitly as ContradictoryEvidence, alongside the existing EvidenceMissing
case. Keep Completed only for non-conflicting linkage states, so the
matched-sessions conflict path cannot report successful completion.

@@ -0,0 +1,313 @@
{"_comment": "SYNTHETIC FIXTURE: user-driven Autopilot channel, happy path",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The fixture comment contradicts the scenario.

The comment says "happy path". This scenario is completed-without-esp-bundle, and its test asserts the opposite: reaching the handoff without ESP evidence is not a completed deployment. The comment reads as copied from user-driven-success-through-esp-handoff.

-{"_comment": "SYNTHETIC FIXTURE: user-driven Autopilot channel, happy path",
+{"_comment": "SYNTHETIC FIXTURE: user-driven Autopilot channel reaching the handoff, with no ESP evidence captured",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{"_comment": "SYNTHETIC FIXTURE: user-driven Autopilot channel, happy path",
{"_comment": "SYNTHETIC FIXTURE: user-driven Autopilot channel reaching the handoff, with no ESP evidence captured",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/autopilot-channel/current/autopilot-events.json`
at line 1, Update the fixture’s _comment metadata to describe the
completed-without-ESP-bundle scenario accurately, removing the “happy path”
wording and distinguishing it from user-driven success through ESP handoff.

Comment on lines +1 to +4
<!-- SYNTHETIC FIXTURE: an MDM diagnostics report section truncated mid-write -->
<MdmDiagnosticsReport schemaVersion="1">
<Autopilot>
<Profile id="11111111-2222-3333-4444-5555

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs --items all
rg -n -C 6 --glob '*.rs' \
  'malformed-report-section|autopilot-report\.json|serde_json|mdmDiagnosticsReport|MdmDiagnosticsReport' \
  crates/cmtraceopen-parser

Repository: adamgell/cmtraceopen

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test harness ---'
sed -n '1,125p' crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs
sed -n '410,435p' crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs

printf '%s\n' '--- support loaders and scenario references ---'
rg -n -C 8 'fn (load_json|bundle|scenario_root)|load_json\(|scenario_root\(|malformed-report-section|autopilot-report' \
  crates/cmtraceopen-parser/tests crates/cmtraceopen-parser/src

printf '%s\n' '--- candidate parser declarations and call sites ---'
rg -n -C 6 'parse.*report|report.*parse|AutopilotBundleInput|mdm|Mdm|Raw.*bytes|source_path|extension' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot \
  crates/cmtraceopen-parser/tests/support.rs

Repository: adamgell/cmtraceopen

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- relevant test code ---'
sed -n '55,125p' crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs
sed -n '418,432p' crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs

printf '%s\n' '--- support module location and loader ---'
fd -t f -i 'support' crates/cmtraceopen-parser/tests
rg -n -C 12 'fn load_json|pub fn load_json|fn bundle|include_bytes|read_to_string|read\(' \
  crates/cmtraceopen-parser/tests

printf '%s\n' '--- autopilot parser API and report handling ---'
rg -n -C 10 'pub fn|fn .*parse|MdmDiagnosticsReport|mdm|report' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot

Repository: adamgell/cmtraceopen

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Autopilot source model and reducer call graph ---'
rg -n -C 12 'struct AutopilotSourceInput|enum Autopilot|AutopilotSourceInput|reduce_autopilot_bundle|family.*mdm|mdm.*family|diagnosticReport|mdmReport|content' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot

printf '%s\n' '--- malformed fixture manifest and expected contract ---'
sed -n '1,90p' crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.json
sed -n '1,180p' crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json

printf '%s\n' '--- exact malformed fixture bytes and related parser declarations ---'
wc -c crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.json
rg -n -C 15 'malformed|Mdm|mdm|report section|diagnostic report' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot \
  crates/cmtraceopen-parser/src/intune

Repository: adamgell/cmtraceopen

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- reducer entry and source dispatch ---'
sed -n '1,280p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs

printf '%s\n' '--- malformed scenario manifest ---'
cat crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.json

printf '%s\n' '--- malformed scenario expectation ---'
cat crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json

Repository: adamgell/cmtraceopen

Length of output: 15538


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- document detection implementation ---'
rg -n -C 24 'pub fn detect_document|fn detect_document|AutopilotDocumentDetection::|content is not a JSON object|AutopilotReport' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot

printf '%s\n' '--- source metadata model ---'
rg -n -C 12 'pub struct AutopilotSourceInput|family:|source_kind|sourceKind' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot

Repository: adamgell/cmtraceopen

Length of output: 50379


Make this fixture use the parser’s JSON document contract. detect_document calls serde_json::from_str before dispatch, so the XML content returns content is not a JSON object and never reaches AutopilotReportDocument parsing. The test currently covers generic JSON rejection, not a malformed report section.

🧰 Tools
🪛 Biome (2.5.6)

[error] 1-1: unexpected character <

(parse)


[error] 1-1: unexpected character !

(parse)


[error] 1-1: Minus must be followed by a digit

(parse)


[error] 1-1: Minus must be followed by a digit

(parse)


[error] 1-1: String values must be double quoted.

(parse)


[error] 1-1: String values must be double quoted.

(parse)


[error] 1-2: End of file expected

(parse)


[error] 1-1: Minus must be followed by a digit

(parse)


[error] 1-1: Minus must be followed by a digit

(parse)


[error] 1-1: Minus must be followed by a digit

(parse)


[error] 1-1: unexpected character >

(parse)


[error] 2-2: unexpected character <

(parse)


[error] 2-2: unexpected character =

(parse)


[error] 2-2: End of file expected

(parse)


[error] 2-2: unexpected character >

(parse)


[error] 3-3: unexpected character <

(parse)


[error] 3-3: String values must be double quoted.

(parse)


[error] 3-3: unexpected character >

(parse)


[error] 4-4: unexpected character <

(parse)


[error] 4-4: String values must be double quoted.

(parse)


[error] 4-4: String values must be double quoted.

(parse)


[error] 4-4: unexpected character =

(parse)


[error] 4-4: Missing closing quote

(parse)

🪛 OpenGrep (1.26.0)

[ERROR] 4-4: Possible credit card number with dashes or spaces detected in source code. Credit card numbers should never be hardcoded or stored in source files. Use a secrets manager or tokenization service instead.

(coderabbit.pii.credit-card-number-dashed)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.json`
around lines 1 - 4, Replace the XML fragment in the malformed Autopilot fixture
with a valid JSON object matching the parser’s document contract, while
preserving a truncated or malformed Autopilot report section for the targeted
parsing failure. Ensure detect_document can deserialize and dispatch the fixture
to AutopilotReportDocument instead of rejecting it as non-JSON.

"Confirm the export was not truncated in transit."
],
"severity": "warning",
"summary": "1 supplied document(s) could not be interpreted, so any signal they carried is absent from this analysis rather than proven absent. content is not a JSON object: expected value at line 1 column 1",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does serde_json guarantee stability of Error Display messages across releases?

💡 Result:

No, serde_json does not guarantee the stability of its Error Display messages across releases [1][2]. In Rust, the Display implementation for an error type is generally considered a part of the crate's public API, but serde_json does not explicitly promise that these specific strings will remain unchanged in future versions [1][3]. The library documentation and official practices indicate that these messages are treated as implementation details [3][4]. If you are building an application that needs to programmatically handle or differentiate between specific JSON parsing failures, relying on the text output of the Display implementation is fragile and discouraged [5]. Instead, it is recommended to use the provided programmatic APIs to inspect errors, such as: 1. Classification: You can use the classify method to determine if an error is due to I/O, syntax, data issues, or unexpected EOF [6][3][7]. 2. Inspection: You can use specific methods like is_io, is_syntax, is_data, or is_eof to check the error category [6][7]. Because error messages may change (for example, to be more descriptive or to fix typos) without a major version increment, code that matches against the exact string output of an error is liable to break during routine updates [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -i 'malformed-report-section|intune|enrollment|autopilot' . | head -80
printf '%s\n' '--- related summary and parse-error code ---'
rg -n -S 'could not be interpreted|content is not a JSON object|expected value|summary|serde_json|malformed-report-section' . --glob '!target/**' | head -200

Repository: adamgell/cmtraceopen

Length of output: 30276


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- fixture files ---'
find crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section -maxdepth 2 -type f -print
printf '%s\n' '--- expected output ---'
cat -n crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json | sed -n '30,50p'
printf '%s\n' '--- parser symbols and error propagation ---'
rg -n -S 'interpret|interpreted|report section|report_section|from_str|to_value|Error|error' crates/cmtraceopen-parser/src/intune crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs
printf '%s\n' '--- dependency declarations and lock entries ---'
rg -n -A5 -B3 'name = "serde_json"|serde_json' Cargo.toml Cargo.lock crates/cmtraceopen-parser/Cargo.toml

Repository: adamgell/cmtraceopen

Length of output: 43335


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- Autopilot source classification ---'
cat -n crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rs | sed -n '150,235p'
printf '%s\n' '--- Autopilot finding construction ---'
cat -n crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rs | sed -n '125,165p'
printf '%s\n' '--- Autopilot fixture test assertions ---'
cat -n crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs | sed -n '400,445p'
printf '%s\n' '--- relevant source comments and error fields ---'
cat -n crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rs | sed -n '120,175p'
cat -n crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rs | sed -n '235,285p'

Repository: adamgell/cmtraceopen

Length of output: 12159


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- malformed fixture manifest ---'
cat -n crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.json
printf '%s\n' '--- source-to-finding call path ---'
cat -n crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs | sed -n '240,315p'
cat -n crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs | sed -n '40,85p'
printf '%s\n' '--- read-only consistency verifier ---'
python3 - <<'PY'
import json
from pathlib import Path

root = Path("crates/cmtraceopen-parser")
fixture = root / "tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section"
expected = json.loads((fixture / "expected.json").read_text())
source = (root / "src/intune/enrollment/windows/autopilot/sources.rs").read_text()
rules = (root / "src/intune/enrollment/windows/autopilot/rules.rs").read_text()

summary = expected["findings"][0]["summary"]
assert summary.endswith("content is not a JSON object: expected value at line 1 column 1")
assert 'format!("content is not a JSON object: {error}")' in source
assert 'format!(' in rules and "could not be interpreted" in rules
print("fixture asserts serde_json Display text:", summary.rsplit(": ", 1)[-1])
print("classifier interpolates serde_json error Display text: yes")
print("finding interpolates classifier detail into summary: yes")
PY

Repository: adamgell/cmtraceopen

Length of output: 8639


Use a stable parse-error reason in the golden output.

The classifier inserts serde_json::Error::to_string() into Malformed.detail, and the finding inserts that detail into the summary. Emit a stable malformed-JSON reason, or keep the raw parser detail outside the asserted summary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json`
at line 41, The malformed-report golden output currently depends on unstable
serde_json parser text. Update the classification and summary path around
Malformed.detail so malformed JSON uses a stable reason, or exclude the raw
parser detail from the asserted summary, then adjust the expected summary to
match the stable output.

Comment on lines +94 to +98
"outcome": "completed",
"phase": "espHandoff",
"profileApplied": true,
"profileCandidateState": "available",
"profileRetrieved": true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate how the Autopilot reducer sets `applied` and `Completed`.
set -euo pipefail

fd -t f 'reducer.rs' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot

fd -t f 'reducer.rs' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot --exec ast-grep outline {} --items all

# Show every assignment to `applied` and every Completed decision.
rg -nP -C 6 '\bapplied\b\s*=|AutopilotOutcome::Completed|ProfileState_Available|profile_applied' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot

# Compare against the event ids the normalizer classifies as an application signal.
rg -nP -C 4 '\b(109|111|153|161)\b' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs

Repository: adamgell/cmtraceopen

Length of output: 20870


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '732,825p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs
sed -n '1280,1372p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs

# Identify the fixture input and print only its event records and the expected fields.
fixture_dir='crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session'
find "$fixture_dir" -maxdepth 1 -type f -print
for f in "$fixture_dir"/*; do
  case "$f" in
    *.json)
      echo "== $f =="
      jq '{events: (.events // .records // empty), outcome: .outcome, profileApplied: .profileApplied, profileCandidateState: .profileCandidateState, profileRetrieved: .profileRetrieved}' "$f" 2>/dev/null || true
      ;;
  esac
done

# Read the reducer source as data and execute a small verifier for the relevant branch.
python3 - <<'PY'
from pathlib import Path

p = Path("crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs")
s = p.read_text()
needles = [
    "AutopilotSignal::ProfileStateChanged",
    "AutopilotProfileStateToken::Available",
    "applied = true",
    "if profile.applied && handoff.esp_observed",
    "AutopilotOutcome::Completed",
]
for n in needles:
    print(f"{n}: {s.count(n)} occurrence(s)")
print("available-state branch contains applied=true:",
      "AutopilotProfileStateToken::Available" in s and
      s.find("AutopilotProfileStateToken::Available") < s.find("applied = true", s.find("AutopilotProfileStateToken::Available")))
PY

Repository: adamgell/cmtraceopen

Length of output: 8729


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fixture_dir='crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session'

for f in "$fixture_dir"/manifest.json "$fixture_dir"/expected.json; do
  echo "== $f =="
  cat -n "$f"
done

echo "== JSON top-level keys =="
python3 - <<'PY'
import json
from pathlib import Path

base = Path("crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session")
for name in ("manifest.json", "expected.json"):
    data = json.loads((base / name).read_text())
    print(name, type(data).__name__)
    if isinstance(data, dict):
        print("keys:", sorted(data))
        for key, value in data.items():
            if isinstance(value, list):
                print(f"{key}: list length={len(value)}")
                if value and isinstance(value[0], dict):
                    print(f"{key}[0] keys:", sorted(value[0]))
PY

Repository: adamgell/cmtraceopen

Length of output: 7949


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== matching fixture files =="
fd -t f . crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot \
  | rg 'matching-autopilot-and-esp-session|completed-without-esp-bundle|autopilot-events|autopilot-report|esp-sessions'

echo "== event 153 message and fixture event ids =="
rg -n -P -C 3 'ProfileState_Available|eventId|event_id|161|153|111|109|103' \
  crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot

echo "== profile-application source contracts =="
rg -n -P -C 5 'ProfileApplication|ProfileApplicationSucceeded|application.*succeed|profile.*appl' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot \
  crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot

Repository: adamgell/cmtraceopen

Length of output: 50377


Gate profileApplied on application evidence.

This fixture contains only events 161 and 153. Event 153 with ProfileState_Available sets applied = true, which produces "profileApplied": true and "outcome": "completed" when ESP evidence exists. Restrict applied to explicit application-success signals, then update the expected terminal fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/expected.json`
around lines 94 - 98, Update the profile application state logic so the
`applied` value used by `profileApplied` is set only by explicit
application-success signals, not by event 153’s `ProfileState_Available` state.
Then update the fixture’s expected terminal fields, including `profileApplied`
and `outcome`, to reflect the absence of application evidence while preserving
the existing phase and retrieval values.

Comment on lines +626 to +647
#[test]
fn update_findings_golden() {
if std::env::var("UPDATE_AUTOPILOT_FINDINGS").is_err() {
return;
}
for scenario in SCENARIOS {
let snapshot = reduce_autopilot_bundle(&bundle(scenario));
let path = scenario_root(scenario).join("expected.json");
let mut expected = load_json(&path);
// Only `findings` is regenerated. `findingIds` stays hand written on
// purpose: it is the cross-check that catches a regeneration which
// quietly changed which rules fire.
expected["findings"] = wire(&snapshot.findings);
write_json(&path, &expected);
}
}

fn write_json(path: &Path, value: &Value) {
let text = serde_json::to_string_pretty(value).expect("golden must serialize") + "\n";
std::fs::write(path, text)
.unwrap_or_else(|error| panic!("{} is writable: {error}", path.display()));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

The golden regenerator races the tests that read the same files.

update_findings_golden is a plain #[test]. The Rust harness runs tests in parallel threads inside one binary. When UPDATE_AUTOPILOT_FINDINGS=1 is set, line 639 truncates and rewrites each expected.json while reduce (line 115) and assert_scenario read the same paths from other threads.

std::fs::write truncates first. A concurrent reader can observe an empty or partial file. The result is either a confusing load_json failure or a comparison against a half-written golden. This happens in exactly the mode the doc comment instructs maintainers to use.

Mark the test #[ignore] so regeneration runs on its own through cargo test -- --ignored, and write through a temporary file plus rename so a reader never sees a truncated golden.

🔒️ Proposed fix
+/// Marked `#[ignore]` so it never runs beside the readers of the same files.
+/// Regenerate with `UPDATE_AUTOPILOT_FINDINGS=1 cargo test --test
+/// intune_windows_autopilot -- --ignored update_findings_golden`.
 #[test]
+#[ignore = "rewrites goldens; run alone"]
 fn update_findings_golden() {
 fn write_json(path: &Path, value: &Value) {
     let text = serde_json::to_string_pretty(value).expect("golden must serialize") + "\n";
-    std::fs::write(path, text)
-        .unwrap_or_else(|error| panic!("{} is writable: {error}", path.display()));
+    let temporary = path.with_extension("json.tmp");
+    std::fs::write(&temporary, text)
+        .unwrap_or_else(|error| panic!("{} is writable: {error}", temporary.display()));
+    std::fs::rename(&temporary, path)
+        .unwrap_or_else(|error| panic!("{} is replaceable: {error}", path.display()));
 }

Also update the doc comment at lines 620 to 625, which currently tells maintainers to set the variable alone.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[test]
fn update_findings_golden() {
if std::env::var("UPDATE_AUTOPILOT_FINDINGS").is_err() {
return;
}
for scenario in SCENARIOS {
let snapshot = reduce_autopilot_bundle(&bundle(scenario));
let path = scenario_root(scenario).join("expected.json");
let mut expected = load_json(&path);
// Only `findings` is regenerated. `findingIds` stays hand written on
// purpose: it is the cross-check that catches a regeneration which
// quietly changed which rules fire.
expected["findings"] = wire(&snapshot.findings);
write_json(&path, &expected);
}
}
fn write_json(path: &Path, value: &Value) {
let text = serde_json::to_string_pretty(value).expect("golden must serialize") + "\n";
std::fs::write(path, text)
.unwrap_or_else(|error| panic!("{} is writable: {error}", path.display()));
}
/// Marked `#[ignore]` so it never runs beside the readers of the same files.
/// Regenerate with `UPDATE_AUTOPILOT_FINDINGS=1 cargo test --test
/// intune_windows_autopilot -- --ignored update_findings_golden`.
#[test]
#[ignore = "rewrites goldens; run alone"]
fn update_findings_golden() {
if std::env::var("UPDATE_AUTOPILOT_FINDINGS").is_err() {
return;
}
for scenario in SCENARIOS {
let snapshot = reduce_autopilot_bundle(&bundle(scenario));
let path = scenario_root(scenario).join("expected.json");
let mut expected = load_json(&path);
// Only `findings` is regenerated. `findingIds` stays hand written on
// purpose: it is the cross-check that catches a regeneration which
// quietly changed which rules fire.
expected["findings"] = wire(&snapshot.findings);
write_json(&path, &expected);
}
}
fn write_json(path: &Path, value: &Value) {
let text = serde_json::to_string_pretty(value).expect("golden must serialize") + "\n";
let temporary = path.with_extension("json.tmp");
std::fs::write(&temporary, text)
.unwrap_or_else(|error| panic!("{} is writable: {error}", temporary.display()));
std::fs::rename(&temporary, path)
.unwrap_or_else(|error| panic!("{} is replaceable: {error}", path.display()));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs` around lines 626
- 647, Prevent concurrent golden-file access in update_findings_golden by
marking the test #[ignore], and update its documentation to instruct maintainers
to run regeneration via cargo test -- --ignored with UPDATE_AUTOPILOT_FINDINGS
set. Change write_json to serialize and write through a temporary file in the
same directory, then rename it over the target so readers never observe
truncation or partial contents.

adamgell and others added 2 commits August 7, 2026 22:17
- Revert rustfmt-only churn the automated agent introduced in files
  unrelated to the Autopilot parser (jamf, elevation, error, collector,
  package_state, skeleton contract tests) back to main's formatting.
- Wire the new NormalizedWindowsEvent.event_version field into the
  test constructors main added since (microsoft_store, compliance).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs`:
- Around line 50-73: Add #[serde(default)] to the event_version field on
NormalizedWindowsEvent so AutopilotEventsDocument can deserialize version 1
events that omit eventVersion, including the shown doctest, while preserving
explicitly provided values.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rs`:
- Around line 76-115: Mark AutopilotSignal and the other listed public enums
with #[non_exhaustive], and apply the same attribute to the public-field structs
AutopilotCaptureMetadata, AutopilotObservation, and AutopilotSnapshot. Leave
AutopilotPhase exhaustive unless its ordering contract is explicitly documented
as semver-stable; preserve all existing derives and enum behavior.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs`:
- Around line 142-167: Update error_code_from_token so 16-digit hexadecimal
HRESULT tokens retain their full 64-bit width when deriving hex, rather than
truncating to 32 bits; continue formatting 32-bit values as before. Add a
regression test in normalize.rs covering 0xFFFFFFFF80070002 and asserting the
complete derived hexadecimal value.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs`:
- Around line 694-700: Filter the identity-evidence loop through the existing
assessability-aware `signal_observations` path before checking `IDENTITY_KEYS`,
rather than iterating `observations` directly. Ensure unreadable or unparsed
records cannot be added to `identity.evidence`, while preserving evidence
collection for available, parsed observations.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/manifest.json`:
- Around line 10-11: Update the unknown-windows-schema-version manifest fixture
so it declares only the unvalidated autopilotSchemaVersion and removes
windowsBuild. Keep the fixture aligned with push_unknown_schema’s schema-only
path, ensuring the rules.rs branch for an unknown schema version without a
windows build is exercised.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/manifest.json`:
- Line 6: Update the manifest description to describe only completion of the
local Autopilot phase and its handoff/link to an ESP session. Remove wording
that claims the overall user-driven deployment is complete or that post-handoff
ESP state is complete, while preserving the existing identity, profile, OOBE,
and shared enrollment identifier details as applicable.

In `@crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs`:
- Line 110: Add a matrix scenario in the test setup around bundle that populates
events with a native event, exercising Ingest::absorb_native_event instead of
the source-input path. Assert the resulting coverage and observation IDs,
including the artifact_id derived from
event.context.provenance.source_artifact_id and observations created without
push_coverage attachment.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7c2227fd-98e6-468f-897e-076837a733f3

📥 Commits

Reviewing files that changed from the base of the PR and between 8551e10 and 7cd55b9.

📒 Files selected for processing (76)
  • crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/rules.rs
  • crates/cmtraceopen-parser/src/intune/device/windows/compliance/reducer.rs
  • crates/cmtraceopen-parser/src/intune/device/windows/compliance/sources.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rs
  • crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rs
  • crates/cmtraceopen-parser/src/intune/normalized.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/self-deploying-contract/current/self-deploying-contract.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/autopilot-channel/current/autopilot-events.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/esp-session-facts/current/esp-sessions.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/mdm-diagnostics-report/current/autopilot-report.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/manifest.json
  • crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs

Comment on lines +50 to +73
//! let events = r#"{
//! "autopilotDocument": "autopilot.events",
//! "documentVersion": 1,
//! "events": [{
//! "context": {
//! "evidenceRef": { "evidenceId": "ap:0", "sourceArtifactId": "autopilot-channel" },
//! "provenance": {
//! "sourceKind": "eventLog", "sourceArtifactId": "autopilot-channel",
//! "filePath": null, "lineNumber": null, "recordNumber": 1,
//! "registry": null, "event": null
//! },
//! "sourceTimestamp": null,
//! "observedAtUtc": "2026-07-31T09:00:00Z",
//! "sensitivity": "public", "parseState": "parsed", "accessState": "available"
//! },
//! "channel": "Microsoft-Windows-ModernDeployment-Diagnostics-Provider/Autopilot",
//! "provider": "Microsoft-Windows-ModernDeployment-Diagnostics-Provider",
//! "eventId": 815,
//! "level": "error",
//! "task": null, "keywords": null, "recordId": 1, "activityId": null,
//! "namedData": [],
//! "message": "ZtdDeviceHasNoAssignedProfile - No profile assigned to the device."
//! }]
//! }"#;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show the NormalizedWindowsEvent declaration with its serde attributes.
fd -a normalized.rs crates/cmtraceopen-parser/src | while IFS= read -r f; do
  echo "== $f =="
  rg -n -B 12 'pub struct NormalizedWindowsEvent' "$f"
  rg -n -A 20 'pub struct NormalizedWindowsEvent' "$f"
done

Repository: adamgell/cmtraceopen

Length of output: 1816


🏁 Script executed:

#!/bin/bash
set -eu

echo "== doctest references =="
rg -n -C 12 'autopilotDocument|NormalizedWindowsEvent|from_str|event_version|eventVersion' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs \
  crates/cmtraceopen-parser/src/intune/normalized.rs

echo "== normalized context definitions =="
rg -n -A 35 -B 8 'pub struct IntuneObservationContext|source_timestamp|observed_at_utc' \
  crates/cmtraceopen-parser/src/intune

echo "== parser dependency versions =="
rg -n -A 6 -B 2 'serde|serde_json' crates/cmtraceopen-parser/Cargo.toml Cargo.lock

Repository: adamgell/cmtraceopen

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -eu

sed -n '44,115p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs
printf '\n== serde declarations ==\n'
sed -n '44,66p' crates/cmtraceopen-parser/src/intune/normalized.rs

python3 - <<'PY'
from pathlib import Path
import re

doctest = Path("crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs").read_text()
struct = Path("crates/cmtraceopen-parser/src/intune/normalized.rs").read_text()

block = re.search(r'let events = r#"(.*?)"#;', doctest, re.S)
if not block:
    raise SystemExit("autopilot doctest JSON block not found")

json_body = block.group(1)
field = re.search(r'\bpub event_version:\s*Option<u32>', struct)
default = re.search(r'#\[serde\([^]]*\bdefault\b[^]]*\)\]', struct)
wire_field = '"eventVersion"'

print(f"event_version declaration present: {bool(field)}")
print(f"struct-level serde(default) present: {bool(default)}")
print(f'doctest contains {wire_field}: {wire_field in json_body}')
print("structural result: missing eventVersion with no serde default"
      if field and not default and wire_field not in json_body
      else "structural result: no omission detected")
PY

Repository: adamgell/cmtraceopen

Length of output: 4095


🏁 Script executed:

#!/bin/bash
set -eu

echo "== autopilot source models and deserialization =="
rg -n -C 8 'struct .*Document|events:|NormalizedWindowsEvent|serde_json::from_str|from_value' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/{models.rs,sources.rs,normalize.rs,reducer.rs}

echo "== reducer entry and source absorption =="
rg -n -A 45 -B 12 'pub fn reduce_autopilot_bundle|content|detect_document|parse' \
  crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/{reducer.rs,sources.rs}

Repository: adamgell/cmtraceopen

Length of output: 50377


Add #[serde(default)] to NormalizedWindowsEvent.event_version. AutopilotEventsDocument deserializes events directly into this type, so version 1 documents that omit eventVersion, including this doctest, currently fail.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rs`
around lines 50 - 73, Add #[serde(default)] to the event_version field on
NormalizedWindowsEvent so AutopilotEventsDocument can deserialize version 1
events that omit eventVersion, including the shown doctest, while preserving
explicitly provided values.

Comment on lines +76 to +115
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AutopilotSignal {
/// Event 160: `AutopilotRetrieveSettings beginning acquisition.`
ProfileAcquisitionStarted,
/// Event 164: internet available to attempt policy download.
NetworkAvailableForDownload,
/// Event 100: `Autopilot policy [name] not found.` Documented as typically
/// transient, so this is explicitly **not** a terminal failure.
ProfilePolicyNotFound,
/// Event 161: `AutopilotManager retrieve settings succeeded.`
ProfileRetrieveSucceeded,
/// Event 153: `AutopilotManager reported the state changed from X to Y.`
ProfileStateChanged,
/// Event 111: `AutopilotRetrieveSettings succeeded.`
ProfileSettingsRetrieved,
/// Events 101, 103, 109: an OOBE setting was retrieved and processed.
OobeSettingObserved,
/// Event 163: download not required, the device is already provisioned.
DeviceAlreadyProvisioned,
/// Event 171: failed to set TPM identity confirmed.
TpmIdentityFailed,
/// Event 172: failed to set the Autopilot profile as available.
ProfileApplicationFailed,
/// Event 807: `ZtdDeviceIsNotRegistered`.
DeviceNotRegistered,
/// Event 809: the assigned profile no longer exists.
AssignedProfileMissing,
/// Event 815: no profile assigned and no tenant default.
NoAssignedProfile,
/// Event 908: serial number or product key mismatch.
IdentityMismatch,
/// A diagnostics-report or supplied-fact section, classified by its own
/// declared section kind rather than by an event ID.
ReportSection,
/// A normalized ESP session fact supplied by the sibling reducer.
EspSessionFact,
/// A record from a validated channel carrying no documented meaning.
Unclassified,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Mark the closed public enums and the input/output structs #[non_exhaustive] before this contract ships.

AutopilotSignal is public, closed, and exhaustively matchable by downstream code. The module doc at line 5 states this table covers 16 documented event IDs, and Microsoft adds event IDs. The next documented ID forces a new variant, which breaks every downstream match and requires a major version bump of the published crate.

The same applies to AutopilotTimezoneState, AutopilotTimeBasis, AutopilotRegistrationState, AutopilotProfileCandidateState, AutopilotEspLinkState, AutopilotCorrelationKeyKind, AutopilotConflictKind, AutopilotOutcome, and to the all-public-field structs AutopilotCaptureMetadata, AutopilotObservation, and AutopilotSnapshot. This PR already demonstrates the struct case: adding event_version to NormalizedWindowsEvent broke four call sites inside this repository alone.

The doc at lines 25-26 already limits additivity to the raw-preserving enums. #[non_exhaustive] makes that promise enforceable by the compiler instead of by convention.

Note the exception: keep AutopilotPhase exhaustive if Ord is part of the contract, or document that variant ordering is itself the semver commitment.

♻️ Proposed change for `AutopilotSignal`
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
+#[non_exhaustive]
 pub enum AutopilotSignal {

As per path instructions: "Treat every public item as a semver commitment: flag breaking changes to public types, signatures, or enum variants".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum AutopilotSignal {
/// Event 160: `AutopilotRetrieveSettings beginning acquisition.`
ProfileAcquisitionStarted,
/// Event 164: internet available to attempt policy download.
NetworkAvailableForDownload,
/// Event 100: `Autopilot policy [name] not found.` Documented as typically
/// transient, so this is explicitly **not** a terminal failure.
ProfilePolicyNotFound,
/// Event 161: `AutopilotManager retrieve settings succeeded.`
ProfileRetrieveSucceeded,
/// Event 153: `AutopilotManager reported the state changed from X to Y.`
ProfileStateChanged,
/// Event 111: `AutopilotRetrieveSettings succeeded.`
ProfileSettingsRetrieved,
/// Events 101, 103, 109: an OOBE setting was retrieved and processed.
OobeSettingObserved,
/// Event 163: download not required, the device is already provisioned.
DeviceAlreadyProvisioned,
/// Event 171: failed to set TPM identity confirmed.
TpmIdentityFailed,
/// Event 172: failed to set the Autopilot profile as available.
ProfileApplicationFailed,
/// Event 807: `ZtdDeviceIsNotRegistered`.
DeviceNotRegistered,
/// Event 809: the assigned profile no longer exists.
AssignedProfileMissing,
/// Event 815: no profile assigned and no tenant default.
NoAssignedProfile,
/// Event 908: serial number or product key mismatch.
IdentityMismatch,
/// A diagnostics-report or supplied-fact section, classified by its own
/// declared section kind rather than by an event ID.
ReportSection,
/// A normalized ESP session fact supplied by the sibling reducer.
EspSessionFact,
/// A record from a validated channel carrying no documented meaning.
Unclassified,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum AutopilotSignal {
/// Event 160: `AutopilotRetrieveSettings beginning acquisition.`
ProfileAcquisitionStarted,
/// Event 164: internet available to attempt policy download.
NetworkAvailableForDownload,
/// Event 100: `Autopilot policy [name] not found.` Documented as typically
/// transient, so this is explicitly **not** a terminal failure.
ProfilePolicyNotFound,
/// Event 161: `AutopilotManager retrieve settings succeeded.`
ProfileRetrieveSucceeded,
/// Event 153: `AutopilotManager reported the state changed from X to Y.`
ProfileStateChanged,
/// Event 111: `AutopilotRetrieveSettings succeeded.`
ProfileSettingsRetrieved,
/// Events 101, 103, 109: an OOBE setting was retrieved and processed.
OobeSettingObserved,
/// Event 163: download not required, the device is already provisioned.
DeviceAlreadyProvisioned,
/// Event 171: failed to set TPM identity confirmed.
TpmIdentityFailed,
/// Event 172: failed to set the Autopilot profile as available.
ProfileApplicationFailed,
/// Event 807: `ZtdDeviceIsNotRegistered`.
DeviceNotRegistered,
/// Event 809: the assigned profile no longer exists.
AssignedProfileMissing,
/// Event 815: no profile assigned and no tenant default.
NoAssignedProfile,
/// Event 908: serial number or product key mismatch.
IdentityMismatch,
/// A diagnostics-report or supplied-fact section, classified by its own
/// declared section kind rather than by an event ID.
ReportSection,
/// A normalized ESP session fact supplied by the sibling reducer.
EspSessionFact,
/// A record from a validated channel carrying no documented meaning.
Unclassified,
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rs`
around lines 76 - 115, Mark AutopilotSignal and the other listed public enums
with #[non_exhaustive], and apply the same attribute to the public-field structs
AutopilotCaptureMetadata, AutopilotObservation, and AutopilotSnapshot. Leave
AutopilotPhase exhaustive unless its ordering contract is explicitly documented
as semver-stable; preserve all existing derives and enum behavior.

Source: Path instructions

Comment on lines +142 to +167
pub fn error_code_from_token(raw: &str) -> IntuneErrorCode {
let trimmed = raw.trim();
let decimal = if let Some(hex) = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
{
u64::from_str_radix(hex, 16)
.ok()
.and_then(|value| u32::try_from(value).ok())
.map(|value| i64::from(value as i32))
.or_else(|| u64::from_str_radix(hex, 16).ok().map(|value| value as i64))
} else {
trimmed.parse::<i64>().ok()
};
let hex = decimal.and_then(|value| {
u32::try_from(value)
.ok()
.or_else(|| i32::try_from(value).ok().map(|value| value as u32))
.map(|value| format!("0x{value:08X}"))
});
IntuneErrorCode {
raw: trimmed.to_owned(),
decimal,
hex,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare every HRESULT/error-code token parser and its tests.
rg -n -C 20 --type=rust 'fn (parse_error_code|error_code_from_token)\s*\(' crates
echo '=== width assertions ==='
rg -n -C 4 --type=rust 'FFFFFFFF|:016X|:08X|not_truncated' crates

Repository: adamgell/cmtraceopen

Length of output: 32619


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '=== normalize parser and tests ==='
sed -n '80,175p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
sed -n '250,330p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
printf '%s\n' '=== shared value definition and parser callers ==='
rg -n -C 8 'struct IntuneErrorCode|error_code_from_token|extract_error_code' crates/cmtraceopen-parser/src
printf '%s\n' '=== standalone behavior probe ==='
python3 - <<'PY'
def current(raw):
    trimmed = raw.strip()
    if trimmed.lower().startswith("0x"):
        digits = trimmed[2:]
        try:
            value = int(digits, 16)
        except ValueError:
            decimal = None
        else:
            if value <= 0xffffffff:
                decimal = (value & 0xffffffff)
                if decimal >= 0x80000000:
                    decimal -= 0x100000000
            else:
                decimal = value - (1 << 64) if value >= (1 << 63) else value
    else:
        try:
            decimal = int(trimmed)
        except ValueError:
            decimal = None
    if decimal is None:
        hex_value = None
    elif 0 <= decimal <= 0xffffffff:
        hex_value = f"0x{decimal:08X}"
    elif -(1 << 31) <= decimal <= (1 << 31)-1:
        hex_value = f"0x{decimal & 0xffffffff:08X}"
    else:
        hex_value = None
    return decimal, hex_value

for token in ("0x80070002", "0xFFFFFFFF80070002", "0XFFFFFFFF80070002",
              "-2147024894", "4294967295", "0xFFFFFFFFFFFFFFFF"):
    print(token, "=>", current(token))
PY

Repository: adamgell/cmtraceopen

Length of output: 26006


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '=== normalize parser and tests ==='
sed -n '80,175p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
sed -n '250,330p' crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs
printf '%s\n' '=== shared value definition and parser callers ==='
rg -n -C 8 'struct IntuneErrorCode|error_code_from_token|extract_error_code' crates/cmtraceopen-parser/src
printf '%s\n' '=== standalone behavior probe ==='
python3 - <<'PY'
def current(raw):
    trimmed = raw.strip()
    if trimmed.lower().startswith("0x"):
        digits = trimmed[2:]
        try:
            value = int(digits, 16)
        except ValueError:
            decimal = None
        else:
            if value <= 0xffffffff:
                decimal = value
                if decimal >= 0x80000000:
                    decimal -= 0x100000000
            else:
                decimal = value - (1 << 64) if value >= (1 << 63) else value
    else:
        try:
            decimal = int(trimmed)
        except ValueError:
            decimal = None
    if decimal is None:
        hex_value = None
    elif 0 <= decimal <= 0xffffffff:
        hex_value = f"0x{decimal:08X}"
    elif -(1 << 31) <= decimal <= (1 << 31)-1:
        hex_value = f"0x{decimal & 0xffffffff:08X}"
    else:
        hex_value = None
    return decimal, hex_value

for token in ("0x80070002", "0xFFFFFFFF80070002", "0XFFFFFFFF80070002",
              "-2147024894", "4294967295", "0xFFFFFFFFFFFFFFFF"):
    print(token, "=>", current(token))
PY

Repository: adamgell/cmtraceopen

Length of output: 26006


Preserve 64-bit HRESULT values in error_code_from_token

hresult_re accepts 16-digit hexadecimal tokens, but error_code_from_token("0xFFFFFFFF80070002") produces hex = Some("0x80070002"). This loses the upper 32 bits from the derived public field. Preserve the captured width when formatting hexadecimal values, and add a regression test for this token in normalize.rs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rs`
around lines 142 - 167, Update error_code_from_token so 16-digit hexadecimal
HRESULT tokens retain their full 64-bit width when deriving hex, rather than
truncating to 32 bits; continue formatting 32-bit values as before. Add a
regression test in normalize.rs covering 0xFFFFFFFF80070002 and asserting the
complete derived hexadecimal value.

Comment on lines +694 to +700
for observation in observations.iter().filter(|observation| {
IDENTITY_KEYS
.iter()
.any(|key| observation.named(key).is_some())
}) {
evidence.push(observation.evidence_ref());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Identity evidence bypasses the assessability gate and inflates the phase.

This loop iterates observations directly. Every other identity path filters through signal_observations (lines 686 and 690), and single_value filters through distinct_values (line 623).

An observation with access_state != Available or parse_state != Parsed therefore lands in identity.evidence. reduce_phase line 1292 tests !identity.evidence.is_empty(), so an unreadable record alone raises the phase to IdentityObserved. The module doc at lines 10 to 12 states a conclusion needs an explicit record.

🐛 Proposed fix
-    for observation in observations.iter().filter(|observation| {
-        IDENTITY_KEYS
-            .iter()
-            .any(|key| observation.named(key).is_some())
-    }) {
+    for observation in observations.iter().filter(|observation| {
+        is_assessable(observation)
+            && IDENTITY_KEYS
+                .iter()
+                .any(|key| observation.named(key).is_some())
+    }) {
         evidence.push(observation.evidence_ref());
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for observation in observations.iter().filter(|observation| {
IDENTITY_KEYS
.iter()
.any(|key| observation.named(key).is_some())
}) {
evidence.push(observation.evidence_ref());
}
for observation in observations.iter().filter(|observation| {
is_assessable(observation)
&& IDENTITY_KEYS
.iter()
.any(|key| observation.named(key).is_some())
}) {
evidence.push(observation.evidence_ref());
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rs`
around lines 694 - 700, Filter the identity-evidence loop through the existing
assessability-aware `signal_observations` path before checking `IDENTITY_KEYS`,
rather than iterating `observations` directly. Ensure unreadable or unparsed
records cannot be added to `identity.evidence`, while preserving evidence
collection for available, parsed observations.

Comment on lines +10 to +11
"windowsBuild": "10.0.99999.1",
"autopilotSchemaVersion": "3",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This scenario cannot exercise the schema-version gate alone.

Both windowsBuild and autopilotSchemaVersion are unvalidated here, so push_unknown_schema fires through its windows_build.is_some() branch and the schema-only path stays untested. That is why the rules.rs gap at lines 57 to 62 survives the suite. Cover it with a capture that declares only an unvalidated autopilotSchemaVersion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/manifest.json`
around lines 10 - 11, Update the unknown-windows-schema-version manifest fixture
so it declares only the unvalidated autopilotSchemaVersion and removes
windowsBuild. Keep the fixture aligned with push_unknown_schema’s schema-only
path, ensuring the rules.rs branch for an unknown schema version without a
windows build is exercised.

"syntheticFixture": true,
"scenario": "user-driven-success-through-esp-handoff",
"workload": "enrollment/windows/autopilot",
"description": "A complete user-driven deployment: identity read, profile retrieved and applied, OOBE settings observed, and an explicit handoff into an ESP session that shares an enrollment identifier.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe local Autopilot completion only.

Line 6 calls this a complete user-driven deployment. The expected descriptor states that all post-handoff state belongs to ESP. The supplied ESP session records only deviceSetup and has no terminal ESP outcome. Change the description to state that the local Autopilot phase completed and linked to an ESP session.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@crates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/manifest.json`
at line 6, Update the manifest description to describe only completion of the
local Autopilot phase and its handoff/link to an ESP session. Remove wording
that claims the overall user-driven deployment is complete or that post-handoff
ESP state is complete, while preserving the existing identity, profile, OOBE,
and shared enrollment identifier details as applicable.

timezone: optional_string(&capture["timezone"]),
},
sources,
events: Vec::new(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No scenario exercises the native-event input path.

bundle always sets events: Vec::new(), so Ingest::absorb_native_event (reducer.rs line 310) is never reached by the matrix. That path differs from the source path in two observable ways: it derives artifact_id from event.context.provenance.source_artifact_id, and it produces observations that no push_coverage call can attach to, because coverage is collected before native events are absorbed.

Add one scenario that supplies events directly, and assert the resulting coverage and observation ids.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cmtraceopen-parser/tests/intune_windows_autopilot.rs` at line 110, Add
a matrix scenario in the test setup around bundle that populates events with a
native event, exercising Ingest::absorb_native_event instead of the source-input
path. Assert the resulting coverage and observation IDs, including the
artifact_id derived from event.context.provenance.source_artifact_id and
observations created without push_coverage attachment.

@adamgell
adamgell merged commit b28dfcb into main Aug 8, 2026
16 checks passed
@adamgell
adamgell deleted the copilot/implement-autopilot-evidence-parser branch August 8, 2026 12:21
adamgell added a commit that referenced this pull request Aug 9, 2026
…t lane (#531)

* fix(intune): gate every Autopilot reduction path on assessability

ADR-001: non-assessable evidence cannot produce a terminal conclusion.
reduce_profile iterated observations raw, so a capped or unparsed success
record could set retrieved/applied and, with an observed ESP handoff,
prove Completed while the matching failure branch was filtered. The
identity evidence loop had the same bypass and inflated the phase to
IdentityObserved from an unreadable record.

Closing the class, not the instance: sections_of now gates report
sections on their own declared context (covering identity, profile,
handoff, and outcome section paths), and the conflict-mismatch loop,
correlation-key extraction, linkage evidence attribution, and the
time-overlap probe are gated the same way. time_basis stays deliberately
unfiltered because there the unfiltered scan is the conservative
direction; documented at is_assessable.

Also per ADR-003: reduce_outcome now matches a Conflicting ESP linkage
explicitly and returns ContradictoryEvidence. The multi-session match
path records no AutopilotConflict, so the empty-conflicts gate above it
let an ambiguous session identity fall through to Completed.

Refs #362, PR #450.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): explain withheld and conflicting Autopilot outcomes

Two silent-outcome gaps in the findings rules:

- push_unknown_schema gated its capture branch on windows_build alone,
  so a capture declaring only an unvalidated autopilotSchemaVersion
  reduced to UnknownSchema with every terminal rule suppressed and no
  finding saying why. The gate now covers both declared values and the
  summary names whichever failed validation.

- A Conflicting ESP linkage reached through distinct keys matching
  distinct sessions records no AutopilotConflict, so no rule explained
  it. push_esp_link_conflicting covers exactly that path; the
  single-key-many-sessions path stays with push_contradictory_evidence.

ADR-001 (withheld semantics must still be explained) and ADR-003
(conservative representation of unresolved contradictions).

Refs #362, PR #450.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): stop collector placeholders classifying as a declared timezone

windows_zone_re accepted any alphabetic run of three or more characters,
so placeholders like 'unknown', 'not recorded', and 'Unavailable'
classified as Declared. That upgraded time_basis to Utc, raised
reduce_confidence to High, and allowed the TimeOnlyCandidate ESP join
the module contract refuses when the timezone is unrecognizable
(ADR-002: timestamp proximity alone never creates strong correlation).

Every Windows time zone identifier ends in 'Time', so the shape check
anchors on that suffix; UTC and offset forms were already covered by
utc_offset_re.

Also makes detect_document's malformed detail a stable reducer-authored
sentence: serde_json does not guarantee its error Display output across
releases, and the detail flows into golden-asserted finding summaries.

Refs #362, PR #450.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): normalize every whole-value mask in the Autopilot export

The module contract promises every whole-value mask is computed over the
trimmed, lowercased value, but four sites called stable_token on the raw
value: the ESP matched keys, sensitive named-data values, and both
conflict-value shapes. A sensitive value arriving in a different casing
or with surrounding space therefore masked to a different token than the
identity field it should visibly equal, destroying the cross-field
correlation the projection exists to preserve (ADR-004: same-scope
redaction preserves intended equality). All four sites now go through
mask_value, which also owns the is_token idempotency check.

Also corrects the opaque-blob doc comment (the regex bound is 40, not
32) and pins the deliberate HRESULT canonicalization in normalize.rs:
a 64-bit sign-extended token derives its canonical 32-bit hex while the
raw token survives verbatim, and a genuinely 64-bit value gains no
fabricated 32-bit form.

Refs #362, PR #450.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(intune): adversarial coverage and safe golden regeneration for Autopilot

New inline scenarios pin the hardened invariants: non-assessable success
and identity records cannot prove progress or raise the phase (ADR-001),
a Conflicting ESP linkage reduces to ContradictoryEvidence with an
explaining finding (ADR-003), a schema-version-only unknown capture is
explained, and the native-event input path derives its artifact from the
event's own provenance.

Fixture corrections, each documented in its expected.json assertions:

- malformed-report-section now carries a correctly tagged report
  document with a mangled sections payload, so it exercises the
  report-payload contract instead of generic non-JSON rejection, and its
  golden no longer pins serde_json's unstable error text.
- unknown-windows-schema-version's summary now names the unvalidated
  schema version alongside the build.
- completed-without-esp-bundle's evidence comment no longer claims a
  happy path, and the user-driven manifest describes local-phase
  completion only; post-handoff state belongs to ESP.

update_findings_golden is now #[ignore]d so it never rewrites goldens
beside the tests reading the same files, and write_json goes through a
temp file plus rename so a reader can never observe a truncated golden.

Refs #362, PR #450.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): let a recorded failure on a non-assessable section block Autopilot success

The assessability gate in sections_of hid every non-assessable report
section from every consumer, including the Failed probes in
reduce_outcome. A capped or unparsed profileApplication section whose
outcome was failed became invisible, and sibling assessable evidence
could then complete the enrollment at high confidence over a failure
that was on record.

The gate is now direction-aware (ADR-001 cuts both ways): sections_of
still admits only assessable sections, so nothing non-assessable can
prove progress or a terminal cause, and the new
recorded_non_assessable_failure_sections iterator carries an explicitly
recorded Failed/Mismatch outcome to the success branch of
reduce_outcome, which then returns InsufficientEvidence instead of
Completed/HandoffReachedEspEvidenceMissing. The recorded failure is
never silent: the new autopilot-non-assessable-failure-recorded finding
(low confidence, warning) cites the section and asks for a readable
re-collection.

NotFound and Retrying stay gated in both directions on purpose: those
are absence or transient statements, and absence in a partial capture
proves nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): keep non-assessable correlation keys for ESP conflict detection

autopilot_keys gated every key on assessability, so a key carried only
by a capped observation vanished entirely. When that key was the one
binding a second ESP session, matched_sessions shrank from two to one
and Conflicting collapsed into Linked into Completed: a non-assessable
record silently upgrading the conclusion.

Keys are the one input where more evidence is more conservative: every
additional key can only widen the entangled-session set. So the key set
is now split by AutopilotKeyGate. Proving keys (assessable only) are
still the only ones that can produce Linked and the Completed outcome
behind it; Detecting keys (all observations) feed the multi-session
conflict check, which runs before any positive linkage and returns
Conflicting naming every detected session. A linkage whose only key
rides a non-assessable observation stays NotObserved/TimeOnlyCandidate,
pinned by its own test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(intune): pin the matched_keys masking site as case-normalizing on its own

The masking loop at the matched_keys site of the redacted export was
behaviorally a no-op because autopilot_keys lowercases every key value
first, and nothing tested either half of that coincidence. Both halves
are now a contract: the reducer hands the projection lowercase values,
and the projection masks a mixed-case value of the same key to the same
token even though the reducer never produces one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): close the remaining Autopilot review should-fixes

Four coordinated corrections from the PR #531 consolidated review:

- distinct_values now groups case-insensitively with a deterministic
  representative casing (lexicographically smallest sighting). Serials
  and GUIDs are case-insensitive identities, and the redacted export
  masks the trimmed lowercased value, so a case-only difference exported
  as '2 distinct values' over two identical tokens -- a conclusion that
  changed under redaction (ADR-004). A case-only difference is no longer
  a conflict; genuinely distinct values still are, both pinned.

- The shared-identifier next_evidence_request now survives the time
  gate: it is keyed on 'ESP facts supplied but not explicitly linked'
  (NotObserved or TimeOnlyCandidate) instead of on TimeOnlyCandidate
  alone, so narrowing the assessable overlap window can no longer erase
  the one step that advances the diagnosis.

- classify_timezone: the windows_zone_re comment no longer claims every
  Windows zone id ends in 'Time' (UTC/UTC+12 are registry ids too, and
  localized StandardNames only degrade conservatively), and a small
  case-insensitive placeholder denylist (Local Time, Device Local Time,
  System Time) closes the placeholders the suffix anchor let through.

- push_esp_link_conflicting acknowledges the legitimate two-real-
  sessions case: a reimaged or re-enrolled device provisions more than
  once, so the finding now says which situation the reader may be in and
  recommends per-attempt analysis before re-collection.

Also appends the 153/172 application-evidence reasoning to the
matching-autopilot-and-esp-session golden's assertions, the standard the
PR sets for contested goldens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): let a recorded failure on a non-assessable event block Autopilot success

The direction-aware assessability gate from the previous round covered
report SECTIONS only. A capped or raw EVENT carrying a documented
failure signal was still silently dropped by the is_assessable filters,
so a bundle with an assessable success path (161 + 153-into-Available +
an assessable espHandoff section) plus a capped event 172 reduced to a
success-family outcome with no finding naming the failure. The class
covers every documented failure signal: 171, 172, 807, 809, 815, 908.

The event side now mirrors the section pattern exactly. The new
recorded_non_assessable_failure_observations iterator selects the
non-assessable observations whose signal is_terminal_failure -- the
symmetry rule being that any record strong enough to fail the enrollment
when readable is strong enough to block its success when unreadable --
and the success branch of reduce_outcome short-circuits to
InsufficientEvidence over either record shape, never a terminal failure
(ADR-001 cuts both ways). ProfilePolicyNotFound (100) stays out for the
same reason the section iterator excludes NotFound/Retrying: documented
transient, not a recorded failure.

push_non_assessable_failure_recorded widens to cite both shapes, so the
recorded failure is never silent; an assessable event 172 still produces
the terminal ProfileApplicationFailure untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): honor the matched_keys contract and hardwareHash case-sensitivity

Two CodeRabbit findings on the Conflicting linkage path and value
grouping, both verified against the code before fixing:

- The distinct-keys-to-distinct-sessions Conflicting return exported its
  detecting keys as matched_keys, but AutopilotEspLinkage documents
  matched_keys as empty for every non-Linked state. Detection is not a
  match; the keys stay internal so ambiguity evidence cannot be read as
  proof of a link.

- distinct_values case-folded every named value, including hardwareHash,
  whose Base64 payload is case-sensitive. Two genuinely different hashes
  differing only by case collapsed into one group that single_value then
  published as corroborated identity. Case-insensitive grouping is now
  an explicit allowlist (CASE_INSENSITIVE_VALUE_KEYS) of verified
  case-insensitive Windows identities; every other key compares exactly,
  the conservative direction. All keys exported through detect_conflicts
  remain on the allowlist, preserving the ADR-004 redaction guarantee.

Both fixes landed test-first: the new regression tests failed on the
prior behavior for exactly the reported reasons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci(coderabbit): sync review config with main so the approve gate can run

This branch forked before main's 96b841c enabled
reviews.request_changes_workflow, and CodeRabbit resolves the config
from the PR branch: the @coderabbitai approve command on PR #531
reported "Approval skipped: request-changes workflow disabled". Copy
main's .coderabbit.yaml verbatim so the file carries zero net diff
against main and the formal approval node can be produced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(intune): close three Hermes charter P1 findings on Autopilot

Address the three blocking semantic findings from the Hermes charter review
of PR #531, each TDD'd (RED first with Hermes's exact inputs).

1. ADR-001 finding side: `signal_evidence` (rules.rs) now requires
   `is_assessable`, so a capped/malformed event 171 can no longer promote
   `autopilot-identity-registration-mismatch` to a High/Blocker finding the
   reducer's own outcome gate already withholds. The assessability check is
   hoisted to a single `AutopilotObservation::is_assessable` method shared by
   the reducer's free fn and the finding-side helpers so the boundary cannot
   drift. Assessable-171 still fires (regression pinned).

2. ADR-004 redaction: `opaque_blob_re` drops its `\b` anchors. The Base64
   alphabet includes `=`, `+`, `/` (non-word), so a trailing `\b` could not
   close a match on a padded/punctuation-terminated hardware hash and left its
   tail exposed. The greedy, leftmost, >=40 match now consumes the whole
   contiguous run while still excluding the 36-char GUID and short HRESULT, and
   the `[blob:...]` token cannot re-match (idempotent).

3. ADR-003 chronology: `reduce_profile` no longer lets an unlinked later
   success (161/153) silently erase an earlier explicit negative (815/809).
   Positive evidence and explicit negatives are tracked apart and reconciled
   from sets (never vector order); a negative is erased only when it shares an
   `activityId` retry-linkage key with an Available-raising success. Unlinked
   stays conservative (NoProfileCandidate, not Completed); linked completes.

Gates: cargo test -p cmtraceopen-parser (0 failures), clippy -D warnings,
cargo check --workspace, wasm32 check all clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(intune): correct the section_activity_id doc and tighten the blob test

CodeRabbit's two trivial findings on the Hermes P1 round, both valid.
The section_activity_id doc claimed it lowercased its return value; it
returns verbatim and push_link owns the lowercasing, so the doc now
says where the normalization lives. The Base64 export test asserted
only that the whole blob was absent, which would have passed while a
masked body left its punctuation tail behind - the exact partial match
the old word-boundary pattern produced - so it now asserts the body
and the dangling tail are gone too.

Verified: cargo test -p cmtraceopen-parser 2150 passed 0 failed
(autopilot suite 47 passed), clippy -D warnings clean.

Refs #362

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
adamgell added a commit that referenced this pull request Aug 13, 2026
…under Unreleased (#530)

Catch up the Unreleased section with all ten commits merged since the
last changelog update (54d539e / #513):

- Microsoft Store app evidence lane (#358 / #518)
- Reducer Framework v1 governance, ADRs, and charters (#519)
- Windows Autopilot evidence parser outside ESP (#362 / #450)
- Company Portal Windows LocalState logs (#366 / #460)
- Bounded advanced SCCM server capture (#500), folded into the
  existing native SCCM diagnostics path bullet
- Agent tooling / Clairvoyance staff org scaffolding (#516)
- Dependency bumps (quick-xml, time, install-action) and the
  GitHub Sponsors funding link

No version bump: package.json/Cargo.toml/tauri.conf.json remain at
1.5.1 with no new tag, so this stays purely an Unreleased catch-up.


Claude-Session: https://claude.ai/code/session_01A8z5Ysfa5Afts6gVPmHQZj

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Intune Windows enrollment: model Autopilot evidence outside ESP

2 participants