feat(intune): implement Windows Autopilot evidence parser outside ESP (issue #362) - #450
Conversation
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
📝 WalkthroughWalkthroughThe 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. ChangesAutopilot contracts and snapshot model
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
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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.jsonFile contains syntax errors that prevent linting: Line 1: unexpected character Comment |
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock,!Cargo.lock
📒 Files selected for processing (112)
crates/cmtraceopen-parser/src/collector/profile.rscrates/cmtraceopen-parser/src/esp/redaction.rscrates/cmtraceopen-parser/src/esp/reducer.rscrates/cmtraceopen-parser/src/esp/timeline.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rscrates/cmtraceopen-parser/src/intune/normalized.rscrates/cmtraceopen-parser/src/intune/portal/windows/company_portal/package_state/mod.rscrates/cmtraceopen-parser/tests/esp_diagnostics.rscrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/self-deploying-contract/current/self-deploying-contract.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/manifest.jsoncrates/cmtraceopen-parser/tests/intune_skeleton_contract.rscrates/cmtraceopen-parser/tests/intune_windows_autopilot.rscrates/cmtraceopen-parser/tests/support/mod.rssrc-tauri/src/commands/elevation.rssrc-tauri/src/commands/file_ops.rssrc-tauri/src/commands/jamf.rssrc-tauri/src/commands/mod.rssrc-tauri/src/commands/recent_entries.rssrc-tauri/src/commands/system_preferences.rssrc-tauri/src/elevation/mod.rssrc-tauri/src/error.rssrc-tauri/src/esp/process.rssrc-tauri/src/esp/registry.rssrc-tauri/src/esp/system.rssrc-tauri/src/graph_api/esp.rssrc-tauri/src/graph_api/models.rssrc-tauri/src/intune/evtx_parser.rssrc-tauri/src/jamf/connect.rssrc-tauri/src/jamf/detect.rssrc-tauri/src/jamf/mod.rssrc-tauri/src/jamf/models.rssrc-tauri/src/jamf/paths.rssrc-tauri/src/jamf/policy_log.rssrc-tauri/src/lib.rssrc-tauri/src/macos_diag/unified_log.rssrc-tauri/src/menu.rssrc-tauri/src/sysmon/evtx_parser.rssrc-tauri/tests/esp_diagnostics_sources.rssrc-tauri/tests/jamf_environment.rssrc-tauri/tests/jamf_ipc_contract.rssrc-tauri/tests/jamf_known_sources.rssrc-tauri/tests/jamf_parser_robustness.rssrc-tauri/tests/jamf_policy_log_parsing.rssrc-tauri/tests/jamf_real_fixtures.rssrc-tauri/tests/jamf_self_service_log_parsing.rs
| /// 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") | ||
| }) | ||
| } |
There was a problem hiding this comment.
📐 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.
| for key in &mut projected.esp_linkage.matched_keys { | ||
| if !is_token(&key.value) { | ||
| key.value = stable_token(VALUE_KIND, &key.value); | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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) | ||
| } |
There was a problem hiding this comment.
📐 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.
| 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()); | ||
| } | ||
| _ => {} | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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.
| if profile.applied && handoff.esp_observed { | ||
| return match esp_linkage.state { | ||
| AutopilotEspLinkState::EvidenceMissing => { | ||
| AutopilotOutcome::HandoffReachedEspEvidenceMissing | ||
| } | ||
| _ => AutopilotOutcome::Completed, | ||
| }; | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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", | |||
There was a problem hiding this comment.
📐 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.
| {"_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.
| <!-- SYNTHETIC FIXTURE: an MDM diagnostics report section truncated mid-write --> | ||
| <MdmDiagnosticsReport schemaVersion="1"> | ||
| <Autopilot> | ||
| <Profile id="11111111-2222-3333-4444-5555 |
There was a problem hiding this comment.
🎯 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-parserRepository: 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.rsRepository: 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/autopilotRepository: 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/intuneRepository: 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.jsonRepository: 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/autopilotRepository: 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", |
There was a problem hiding this comment.
📐 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:
- 1: https://github.com/serde-rs/json/blob/master/src/error.rs
- 2: https://docs.serde.rs/src/serde_json/error.rs.html
- 3: https://docs.rs/serde_json/latest/serde_json/error/struct.Error.html
- 4: Consider exposing arbitrary_precision number::TOKEN in the stable API serde-rs/json#1019
- 5: Adding serde_json::Error::code() functionnality serde-rs/json#865
- 6: https://docs.rs/serde_json/latest/src/serde_json/error.rs.html
- 7: https://docs.rs/serde_json/latest/serde_json/struct.Error.html
🏁 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 -200Repository: 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.tomlRepository: 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")
PYRepository: 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.
| "outcome": "completed", | ||
| "phase": "espHandoff", | ||
| "profileApplied": true, | ||
| "profileCandidateState": "available", | ||
| "profileRetrieved": true, |
There was a problem hiding this comment.
🎯 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.rsRepository: 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")))
PYRepository: 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]))
PYRepository: 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/autopilotRepository: 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.
| #[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())); | ||
| } |
There was a problem hiding this comment.
🩺 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.
| #[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.
…opilot-evidence-parser
- 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>
|
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. |
There was a problem hiding this comment.
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
📒 Files selected for processing (76)
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/rules.rscrates/cmtraceopen-parser/src/intune/device/windows/compliance/reducer.rscrates/cmtraceopen-parser/src/intune/device/windows/compliance/sources.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/mod.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/models.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/normalize.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/redaction.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/reducer.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/rules.rscrates/cmtraceopen-parser/src/intune/enrollment/windows/autopilot/sources.rscrates/cmtraceopen-parser/src/intune/normalized.rscrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/completed-without-esp-bundle/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/conflicting-profile-session-identifiers/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/deterministic-identity-redaction/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/identity-registration-mismatch/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/incomplete-event-channel/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/invalid-timezone/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/malformed-report-section/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/matching-autopilot-and-esp-session/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/network-retry-without-terminal-proof/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/no-profile-candidate/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-application-failure/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/profile-retrieval-failure/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/evidence/self-deploying-contract/current/self-deploying-contract.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/self-deploying-source-contract-not-captured/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/unknown-windows-schema-version/manifest.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/autopilot-channel/current/autopilot-events.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/esp-session-facts/current/esp-sessions.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/evidence/mdm-diagnostics-report/current/autopilot-report.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/enrollment/windows/autopilot/user-driven-success-through-esp-handoff/manifest.jsoncrates/cmtraceopen-parser/tests/intune_windows_autopilot.rs
| //! 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." | ||
| //! }] | ||
| //! }"#; |
There was a problem hiding this comment.
🗄️ 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"
doneRepository: 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.lockRepository: 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")
PYRepository: 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.
| #[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, | ||
| } |
There was a problem hiding this comment.
📐 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.
| #[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
| 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, | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 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' cratesRepository: 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))
PYRepository: 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))
PYRepository: 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.
| for observation in observations.iter().filter(|observation| { | ||
| IDENTITY_KEYS | ||
| .iter() | ||
| .any(|key| observation.named(key).is_some()) | ||
| }) { | ||
| evidence.push(observation.evidence_ref()); | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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.
| "windowsBuild": "10.0.99999.1", | ||
| "autopilotSchemaVersion": "3", |
There was a problem hiding this comment.
📐 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.", |
There was a problem hiding this comment.
📐 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(), |
There was a problem hiding this comment.
📐 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.
…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>
…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>
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 semanticsnormalize.rs— Maps the 16 Microsoft-documented Autopilot event IDs to typedAutopilotSignalvariants; anything outside that table classifies asUnclassifiedand is retained but never promoted to a terminal conclusionreducer.rs— Pure fold: evidence bundle → immutableAutopilotSnapshot; computes outcome, phase, ESP linkage, conflicts, coverage, and next-artifact requests in a single passrules.rs— Evidence-backed findings only; each finding cites an exact observation or coverage gap and names the next smallest artifactmodels.rs— Typed snapshot contract (12-variantAutopilotOutcome, 7-stageAutopilotPhase,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 correlationFixture 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-autopilotbranch had a passing test suite but eight confirmed semantic defects. All are addressed here:NormalizedWindowsEvent/AutopilotObservationmissing event versionevent_version: Option<u32>added to bothreduce_esp_linkageemittingTimeOnlyCandidateunconditionally when no explicit key matchedtime_basis == UTCand actual normalized-UTC timestamp overlap; otherwiseNotObservedLinkedmatched_sessions.len() > 1→Conflictingbefore theLinkedreturnVALUE_KIND = "redacted"is_token()treating any[foo:bar]string as already-masked[redacted:<16 lowercase hex chars>]recognised as a tokensource_kind_tag,rotation_index,fragment_indexadded toAutopilotSourceInputis_assessable()guard (access_state == Available && parse_state == Parsed) applied to all outcome-driving iterationcargo fmt --check --allfailure across the workspacecargo fmt --allappliedThe
invalid-timezonefixture'sespLinkStateis corrected fromtimeOnlyCandidatetonotObservedto match the fixed reduction; its finding set and assertions are updated accordingly.Summary by CodeRabbit
New Features
Bug Fixes
Tests