Skip to content

feat(intune): model remediation detection and remediation evidence - #400

Merged
adamgell merged 5 commits into
mainfrom
claude/360-intune-windows-remediations
Aug 1, 2026
Merged

feat(intune): model remediation detection and remediation evidence#400
adamgell merged 5 commits into
mainfrom
claude/360-intune-windows-remediations

Conversation

@adamgell

@adamgell adamgell commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Refs #360. Deliberately not Closes — see "What is not done".

What this adds

cmtraceopen_parser::intune::apps::windows::remediations — a semantic analyzer over supplied IME evidence for the Intune Remediations lifecycle. A remediation is a pair: detection decides whether anything is wrong, remediation runs only if it was, and the two halves are reduced separately.

Module Responsibility
sources.rs Artifact classification from file name and the CCM components present
rules.rs Per-record classification; decides nothing
reducer.rs Keyed pair reduction into separate stage outcomes
redaction.rs Deterministic privacy projection
models.rs Public camelCase-serialized contract

The rule everything rests on

An exit code with no stage terminates nothing. 0 means compliant from a detection script and succeeded from a remediation script. Attributing a code to the wrong half inverts the diagnosis.

So: a record that does not name its stage yields no exit token at all, RemediationExitToken::stage is not optional (an unattributed token cannot be constructed), and an AgentExecutor.log supplied without its HealthScripts.log orchestrator produces no transactions rather than guessed ones. The exit_tokens_never_cross_stages test asserts this across four scenarios.

Two smaller distinctions that matter:

  • Invocation is part of the key. A scheduled run and an on-demand run of the same policy are different lifecycles and never pool.
  • Skipped is not NotStarted. It is the stronger claim that detection proved there was nothing to do.

Embedded JSON payloads

Preserved losslessly and marked parsed or not. A malformed payload is reported as malformed, never repaired. A payload whose braces do not close inside one record is not captured at all — fragments are never concatenated across records to complete one.

A defect these fixtures found

A Windows path inside a JSON payload arrives escaped as C:\\Users\\Someone, and the shared profile-segment mask required a single separator — so every such path was exported unmasked. Remediation payloads are the most likely place for that, since detection scripts emit arbitrary data. Fixed in the shared masking with tests.

Shared masking (first commit)

The first commit moves the UPN / user-path / command-line masking into intune::apps::windows::common, so this analyzer and the platform-script analyzer have one implementation rather than two copies that drift. scripts::redact_text stays exported from the same path; no consumer moves. This directly answers the duplication concern raised in review of #386.

Fixture matrix

All 17 scenarios required by the issue are under tests/fixtures/intune/windows/remediations/, each with manifest.json, its evidence, and a written-out expected.json. detection-compliant-remediation-skipped additionally carries expected-full.json, a golden of the complete redacted export (regenerate with UPDATE_REMEDIATION_GOLDEN=1).

Verification

Run from the repository root:

  • cargo test --locked -p cmtraceopen-parser — 434 unit (baseline 407), 22 new intune_windows_remediations, 24 scripts, 27 company-portal, 222 esp, 1 doc test. 0 failed.
  • cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings — clean.

What is not done

The event_tracker.rs consolidation is not in this PR. #360 asks that the relevant HealthScripts/AgentExecutor logic there be consolidated behind the canonical API. It is not, so two rulesets still exist in the crate. This is the same outstanding gap tracked on #359, and it should be closed once for both analyzers rather than half-done twice. Hence Refs, not Closes.

Also out of scope: no native known-source or frontend changes, and no custom-compliance analyzer.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added Windows Intune remediation analysis with detection and remediation states, retries, timeouts, failures, reporting outcomes, and post-remediation status.
    • Added artifact identification and evidence tracking across related logs and script outputs.
    • Added support for malformed, missing, rotated, ambiguous, and unclassified evidence without guessing.
  • Privacy
    • Added deterministic redaction for sensitive paths, credentials, user details, payloads, and observations while preserving useful identifiers.
  • Tests
    • Added comprehensive scenario coverage for remediation outcomes, evidence handling, serialization, and redaction behavior.

adamgell and others added 2 commits July 31, 2026 14:06
Before: `intune::apps::windows::scripts::redaction` owned the UPN, user-profile
and command-line masking regexes outright. A second Windows workload analyzer
needs exactly the same masking, and copying those four regexes would create two
implementations that drift apart -- the class of duplication that already drew
review comment on #386.

This moves the masking primitives to `intune::apps::windows::common`, unchanged.
`scripts::redaction` now calls into it and keeps deciding *what* is sensitive,
which is the part that genuinely differs per analyzer. `scripts::redact_text`
stays exported from the same path it was, so no consumer moves.

This is the right seam because the split is between two different kinds of
decision. Which spans of free text are dangerous is a property of Windows log
text in general. Which fields carry that text is a property of one analyzer's
contract. Only the first is shared.

Verified with `cargo test --locked -p cmtraceopen-parser`: 24
`intune_windows_scripts` integration tests pass unchanged, including the
redaction and golden-export cases, plus 407 unit and 222 esp tests. 0 failed.
`cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings` is
clean.

Refs #360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Before: Intune Remediations had no owner. `intune/event_tracker.rs` recognised
a `HealthScripts` source kind and emitted loose events, but nothing modelled the
pair. There was no way to answer whether detection ran, whether remediation was
required, whether it ran, what it did, or whether any of it was reported.

This adds `cmtraceopen_parser::intune::apps::windows::remediations`, a semantic
analyzer over supplied IME evidence:

- `sources.rs` classifies each artifact from its name *and* the CCM components
  inside it, and recognises retained `{policyId}_{runId}.output` / `.error`
  artifacts by name without ever reading their contents;
- `rules.rs` classifies one record at a time and decides nothing;
- `reducer.rs` groups records into pairs keyed on policy, run, and invocation,
  reducing detection and remediation into *separate* stage outcomes;
- `redaction.rs` masks record text, artifact paths, and embedded payloads.

The rule everything else rests on: **an exit code with no stage terminates
nothing.** `0` means "compliant" from a detection script and "succeeded" from a
remediation script. Attributing a code to the wrong half inverts the diagnosis,
so a record that does not name its stage yields no exit token at all, and an
`AgentExecutor.log` supplied without its `HealthScripts.log` orchestrator
produces no transactions rather than guessed ones. `exit_tokens_never_cross_stages`
asserts this across four scenarios, and `RemediationExitToken::stage` is not
optional, so an unattributed token cannot be constructed.

Invocation is part of the key, so a scheduled run and an on-demand run of the
same policy never pool. `Skipped` is a distinct remediation state from
`NotStarted`: it is the stronger claim that detection proved there was nothing
to do.

Embedded JSON payloads are preserved losslessly and marked parsed or not. A
malformed payload is reported as malformed, never repaired, and a payload whose
braces do not close inside one record is not captured at all -- fragments are
never concatenated across records to complete one.

One defect was found by these fixtures and fixed in the shared masking: a
Windows path inside a JSON payload arrives escaped as `C:\\Users\\Someone`, and
the profile-segment rule required a single separator, so every such path was
exported unmasked. Remediation payloads are the most likely place for that to
happen, since detection scripts emit arbitrary data.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 434 unit (was 407), 22 new
  `intune_windows_remediations` integration, 24 scripts, 27 company-portal,
  222 esp, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

All 17 scenarios required by the issue are present under
`tests/fixtures/intune/windows/remediations/`, each with a manifest, its
evidence, and a written-out `expected.json`.
`detection-compliant-remediation-skipped` additionally carries
`expected-full.json`, a golden of the complete redacted export.

This deliberately does not use `Closes`. The issue also asks that the relevant
`HealthScripts`/`AgentExecutor` logic in `intune/event_tracker.rs` be
consolidated behind the canonical API. That is not done here, so two rulesets
still exist in the crate; the same gap is tracked on #359.

Refs #360

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 18:07
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@adamgell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 07d29d27-4df6-4caf-b846-6082284550db

📥 Commits

Reviewing files that changed from the base of the PR and between eb033db and 5956cbc.

⛔ Files ignored due to path filters (3)
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout-with-retained-artifact/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/later-completion-without-readable-code/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/thread-reuse-after-report/HealthScripts.log is excluded by !**/*.log
📒 Files selected for processing (14)
  • crates/cmtraceopen-parser/src/intune/apps/windows/common/redaction.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout-with-retained-artifact/11111111-1111-4111-8111-111111111111_aaaaaaaa-1111-4111-8111-aaaaaaaaaaa1.error
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout-with-retained-artifact/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout-with-retained-artifact/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/later-completion-without-readable-code/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/later-completion-without-readable-code/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/thread-reuse-after-report/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/thread-reuse-after-report/manifest.json
  • crates/cmtraceopen-parser/tests/intune_windows_remediations.rs
📝 Walkthrough

Walkthrough

Adds a Windows Intune remediation analyzer. It classifies artifacts and records, reduces stage-specific evidence into transactions, applies deterministic privacy redaction, and validates outcomes with comprehensive fixtures and integration tests.

Changes

Intune Windows remediation analysis

Layer / File(s) Summary
Shared Windows redaction
crates/cmtraceopen-parser/src/intune/apps/windows/common/*, crates/cmtraceopen-parser/src/intune/apps/windows/scripts/*
Adds deterministic masking for UPNs, profile paths, and credential values. Windows scripts now use the shared redaction helper.
Remediation contracts and sources
crates/cmtraceopen-parser/src/intune/apps/windows/{mod.rs,remediations/mod.rs}, crates/cmtraceopen-parser/src/intune/apps/windows/remediations/models.rs, crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs
Adds public remediation models, stage-specific outcomes, evidence structures, coverage data, and source classification for rotated logs and retained output artifacts.
Record classification
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs
Extracts scoped identifiers, stages, invocation modes, payloads, exit codes, and lifecycle signals without guessing ambiguous stage attribution.
Bundle reduction and export projection
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/{reducer.rs,redaction.rs}
Groups classified records into transactions, observations, coverage, confidence, and evidence requests. Adds a redacted analysis projection.
Scenario and contract validation
crates/cmtraceopen-parser/tests/intune_windows_remediations.rs, crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/*
Adds fixtures and tests for success, skips, failures, timeouts, retries, missing sources, malformed payloads, rotation boundaries, separate runs, serialization, and redaction.

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

Sequence Diagram(s)

sequenceDiagram
  participant SourceInput
  participant ArtifactClassifier
  participant RecordClassifier
  participant BundleReducer
  participant RedactionProjection
  SourceInput->>ArtifactClassifier: classify artifact metadata
  ArtifactClassifier->>RecordClassifier: provide scoped artifact content
  RecordClassifier->>BundleReducer: provide record classifications
  BundleReducer->>RedactionProjection: provide RemediationAnalysis
  RedactionProjection-->>SourceInput: return redacted analysis
Loading

Possibly related issues

Possibly related PRs

  • adamgell/cmtraceopen#386 — Extends the related Windows Intune analyzer architecture and introduces the shared redaction boundary.
  • adamgell/cmtraceopen#388 — Introduces the documented Windows remediations module implemented by this change.

Suggested labels: enhancement, intune, parser, feature

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: modeling Intune remediation detection and remediation evidence.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@coderabbitai coderabbitai Bot added enhancement New feature or request feature New feature intune Microsoft Intune related parser Log parser related labels Jul 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new pure semantic analyzer under cmtraceopen_parser::intune::apps::windows::remediations to model the paired Intune Remediations lifecycle (detection + remediation) from supplied IME evidence, with strict stage-scoped exit semantics and deterministic redaction. It also factors shared text-masking into intune::apps::windows::common so the platform-scripts and remediations analyzers reuse one implementation.

Changes:

  • Introduces intune::apps::windows::remediations (source classification, record rules, reducer, models, redaction) plus an integration test and a full fixture matrix.
  • Extracts shared deterministic redaction into intune::apps::windows::common::redact_text and re-exports it from the scripts module to preserve the public API.
  • Adds golden/contract tests (including “exit tokens never cross stages” and redaction idempotence) and scenario fixtures with explicit expected.json outputs.

Reviewed changes

Copilot reviewed 47 out of 80 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
crates/cmtraceopen-parser/src/intune/apps/windows/mod.rs Exposes new common and remediations analyzers under Windows Intune workloads.
crates/cmtraceopen-parser/src/intune/apps/windows/common/mod.rs New shared module for common primitives (currently redaction).
crates/cmtraceopen-parser/src/intune/apps/windows/common/redaction.rs Shared deterministic text masking (UPN/user-path/command-line), incl. JSON-escaped path fix + tests.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/mod.rs Re-exports shared redact_text; keeps scripts API stable while moving implementation.
crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs Updates scripts export projection to use shared common::redact_text.
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/mod.rs New remediation analyzer module surface + exports.
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/models.rs New public camelCase-serialized remediation contract types.
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs Artifact classification for remediation evidence (name + component confirmation).
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs Record-level classification rules (stage, keys, exit tokens, payload capture).
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs Reducer that pairs detection/remediation by key and produces stage outcomes + coverage.
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/redaction.rs Remediations export projection applying shared redaction to sensitive text/payloads.
crates/cmtraceopen-parser/tests/intune_windows_remediations.rs New focused integration test over the fixture matrix + golden redacted export.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/manifest.json Fixture manifest for “detection compliant / remediation skipped”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/HealthScripts.log Fixture evidence: HealthScripts (compliant).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/AgentExecutor.log Fixture evidence: AgentExecutor (present but unkeyed).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/expected-full.json Golden full redacted export for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/manifest.json Fixture manifest for “remediation succeeds; post-state compliant”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/HealthScripts.log Fixture evidence: HealthScripts (noncompliant → remediation → compliant + report).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/manifest.json Fixture manifest for “remediation exits nonzero”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/HealthScripts.log Fixture evidence: HealthScripts (remediation exit 3).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/manifest.json Fixture manifest for “detection launch failure”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/HealthScripts.log Fixture evidence: HealthScripts (failed to create process).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/manifest.json Fixture manifest for “detection timeout”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/HealthScripts.log Fixture evidence: HealthScripts (timed out).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/manifest.json Fixture manifest for “remediation launch failure”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/HealthScripts.log Fixture evidence: HealthScripts (remediation failed to create process).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/manifest.json Fixture manifest for “remediation timeout”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/HealthScripts.log Fixture evidence: HealthScripts (remediation timed out).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/manifest.json Fixture manifest for “remediation succeeds; post-state noncompliant”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/HealthScripts.log Fixture evidence: HealthScripts (post-detection noncompliant).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/manifest.json Fixture manifest for “report fails”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/HealthScripts.log Fixture evidence: HealthScripts (send failed).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/manifest.json Fixture manifest for “scheduled vs on-demand separation”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/HealthScripts.log Fixture evidence: HealthScripts (two runs).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/manifest.json Fixture manifest for “retry sequence”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/HealthScripts.log Fixture evidence: HealthScripts (remediation attempts).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-agent-executor/manifest.json Fixture manifest for “missing AgentExecutor is coverage”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-agent-executor/HealthScripts.log Fixture evidence: HealthScripts only.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-agent-executor/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-health-scripts/manifest.json Fixture manifest for “missing HealthScripts stages nothing”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-health-scripts/AgentExecutor.log Fixture evidence: AgentExecutor only.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-health-scripts/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/manifest.json Fixture manifest for “malformed embedded JSON”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/HealthScripts.log Fixture evidence: HealthScripts with malformed payload.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/manifest.json Fixture manifest for rotation boundary behavior.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/HealthScripts-1.log Fixture evidence: rotated HealthScripts part 1.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/HealthScripts.log Fixture evidence: live HealthScripts tail.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/manifest.json Fixture manifest for “same-minute distinct policies”.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/HealthScripts.log Fixture evidence: HealthScripts (two policies, same timestamp).
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/AgentExecutor.log Fixture evidence: AgentExecutor.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/expected.json Expected reduced contract for scenario.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/manifest.json Fixture manifest for deterministic privacy redaction.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/HealthScripts.log Fixture evidence: sensitive values + embedded JSON.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/AgentExecutor.log Fixture evidence: sensitive command-line flag value.
crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/expected.json Expected reduced contract + redaction assertions.

Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs Outdated
…alyzer

1. A block never closed, so a stray record on a reused thread could overwrite a
   terminal outcome. Blocks only split at a stage launch, and nothing ended one
   when a run reported. The agent reuses CCM thread numbers freely, so a later
   record that named no policy of its own was absorbed into whatever block was
   still open, inherited that run's key and stage, and its exit code was applied
   to a transaction it had nothing to do with -- silently inverting a diagnosis
   with no conflict raised, because the record never stated a policy to conflict
   with. Blocks now also open at a policy receipt and close after a report.
   Fixture `thread-reuse-after-report` pins it.

2. A comment promised a safety net that did not exist. It claimed that "two
   policies, or two stages" in one block would refuse to key, but only the
   policy conflict returned early. The behaviour on a stage conflict is
   intentional -- the records share a policy, so the key is safe; the stage is
   not, so it is withheld and those records stay unstaged -- but the comment
   described something else entirely. Corrected to say what the code does and
   why the two conflicts are handled differently.

3. Two different constants named `IME_COMPONENTS` lived in the same module with
   different contents: one gating which records may speak for this workload,
   one confirming that a file is the IME log at all. Renamed to
   `IME_RECORD_SCOPE_COMPONENTS` and `IME_FILE_COMPONENTS`, since the wider file
   list looked like a bug next to the narrower record list.

4. The module's most subtle rule was undocumented and read as an accident: a
   record may inherit its block's stage for *routing* but never for
   *interpreting* an exit code. Spelled out in the module docs with the reason
   -- routing a record to the wrong stage costs an unresolved half, while
   interpreting a code under the wrong stage turns "compliant" into "succeeded"
   -- and pinned with a test.

Reviewed and rejected: that `PowerShell` be accepted as a remediation-scope
component in the primary IME log, on the grounds that the sibling script
analyzer accepts it there. It is exactly because the sibling owns `PowerShell`
in that log that this module must not: accepting it would make platform-script
reporting records become remediation reporting records, which is the
cross-workload contamination the scope gate exists to prevent. Remediation
orchestration is logged under `HealthScripts`. Two tests now pin both
directions, and the reasoning is in the constant's documentation.

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 437 unit, 23
  `intune_windows_remediations`, 24 scripts, 27 company-portal, 222 esp, 1 doc
  test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.
- `git diff --check`: clean.

Refs #360

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

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@adamgell
adamgell requested a review from Copilot July 31, 2026 18:17
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (6)
crates/cmtraceopen-parser/tests/intune_windows_remediations.rs (1)

319-345: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider widening exit_tokens_never_cross_stages coverage.

The doc comment on Line 316 calls this the property "the whole module rests on," but the loop on Line 321-326 checks only 4 scenarios. Other scenarios in this fixture set, such as same-minute-distinct-policies and scheduled-and-on-demand-kept-separate, also carry a non-null detectionExitDecimal and would exercise the detection-side half of this invariant at negligible cost. Add these scenario names to the list to strengthen regression coverage of a core invariant.

♻️ Suggested addition
     for scenario in [
         "remediation-succeeds-post-state-compliant",
         "remediation-exits-nonzero",
         "remediation-succeeds-post-state-noncompliant",
         "retry-sequence",
+        "same-minute-distinct-policies",
+        "scheduled-and-on-demand-kept-separate",
     ] {
🤖 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_remediations.rs` around lines
319 - 345, Expand the scenario list in exit_tokens_never_cross_stages to include
same-minute-distinct-policies and scheduled-and-on-demand-kept-separate,
ensuring the detection-stage invariant is exercised for these fixtures while
preserving the existing assertions and scenarios.
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs (3)

506-520: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the sort-key closure so it does not shadow the key parameter.

The closure at Line 508 is named key, and it shadows the key: RemediationKey parameter for the body of the sort closure. The code compiles, because the parameter is not read inside. A later edit that needs the transaction key there would silently pick up the closure instead.

♻️ Proposed rename
     if all_trustworthy {
         ordered.sort_by(|&left, &right| {
-            let key = |index: usize| {
+            let sort_key = |index: usize| {
                 (
                     records[index]
                         .timestamp
                         .as_ref()
                         .and_then(|t| t.normalized_utc.clone())
                         .unwrap_or_default(),
                     records[index].artifact_index,
                     records[index].record_number,
                 )
             };
-            key(left).cmp(&key(right))
+            sort_key(left).cmp(&sort_key(right))
         });
🤖 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/apps/windows/remediations/reducer.rs`
around lines 506 - 520, Rename the inner sort-key closure in the all_trustworthy
sorting block to avoid shadowing the surrounding key: RemediationKey parameter,
and update its call sites in the comparator. Keep the sorting behavior
unchanged.

142-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Align the comment with the two distinct conflict behaviors.

The comment states that a policy conflict or a stage conflict makes the code refuse to key. The code refuses to key only on conflicting_policy. On conflicting_stage it still assigns resolved_policy_id and resolved_run_id, and only withholds resolved_stage.

The code behavior is the more useful one, because an unstaged but keyed record still contributes reporting and coverage evidence. Correct the comment so the contract is unambiguous.

♻️ Proposed comment correction
-    // Two policies, or two stages, inside one block means our block boundary is
-    // wrong for this agent version. Refuse to key rather than merge.
+    // Two policies inside one block means our block boundary is wrong for this
+    // agent version. Refuse to key rather than merge.
     if conflicting_policy {
         return;
     }
 
     for &index in block {
         if let Some(policy_id) = &policy_id {
             records[index].resolved_policy_id = Some(policy_id.clone());
             records[index].resolved_run_id = run_id.clone();
             records[index].resolved_invocation = invocation;
         }
+        // Two stages inside one block are also a boundary error, but the key
+        // still holds. Withhold only the stage, so no exit code is attributed.
         if !conflicting_stage {
🤖 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/apps/windows/remediations/reducer.rs`
around lines 142 - 159, Update the comment above the conflicting_policy check to
distinguish the two behaviors: conflicting policies prevent keying the block,
while conflicting stages only prevent assigning a resolved stage and still allow
policy and run identifiers to be assigned. Keep the existing control flow
unchanged.

357-357: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider limiting observations to in-scope records.

observations receives one entry per parsed line of every non-output artifact, and each entry clones the full message text at Line 230. Out-of-scope records are included, because analyze_remediation_bundle pushes a PendingRecord for every line at Lines 293-307 regardless of classification.in_scope.

IntuneManagementExtension.log is shared by every workload, so most of its lines are out of scope for remediations. For a large rotated bundle this holds two copies of the whole log in memory, and it enlarges the exported analysis with records that carry signal: Unclassified and no key.

Filtering to_observation to records where classification.in_scope is true, or where the record is keyed or carries a signal, would bound the output. Confirm the fixtures do not depend on the current full-fidelity list before changing it.

♻️ Proposed filter
-    let observations = records.iter().map(to_observation).collect();
+    let observations = records
+        .iter()
+        .filter(|record| record.classification.in_scope)
+        .map(to_observation)
+        .collect();
🤖 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/apps/windows/remediations/reducer.rs` at
line 357, Limit the observations collected in the reducer around
`records.iter().map(to_observation)` to records relevant to remediation
analysis, using `classification.in_scope` or the presence of a key or signal,
while preserving all currently included in-scope records. Review and update
affected fixtures or expectations so they no longer depend on out-of-scope
unclassified entries.
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/redaction.rs (2)

44-49: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Document that parsed describes the pre-redaction text.

redact_payload masks raw_text and copies parsed unchanged. Masking can replace characters inside the JSON, so an exported payload can carry parsed: true next to text that no longer parses as JSON.

Keeping the original verdict is correct, because parsed is evidence about what the agent emitted. A consumer that re-parses raw_text and compares the result against parsed would reach the wrong conclusion. State the contract on the parsed field in models.rs, or in this function.

🤖 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/apps/windows/remediations/redaction.rs`
around lines 44 - 49, Document in the RemediationPayload model or redact_payload
function that parsed records whether the original, pre-redaction raw_text was
valid JSON and is intentionally preserved unchanged after redaction. Keep the
existing parsed value while clarifying that it must not be interpreted as the
parse status of the redacted text.

30-56: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Destructure exhaustively so a new sensitive field cannot escape masking.

All four helpers copy the remaining fields with ..value.clone(). If a later change adds a RemediationClassifiedString field to RemediationArtifact, RemediationObservation, RemediationPayload, or RemediationTransaction, the compiler accepts these helpers unchanged and the new field is exported unmasked.

This module is the privacy boundary for the export, so a compile-time failure is preferable to a silent leak. Replace the functional update with an exhaustive struct pattern in each helper. The compiler then rejects the helper until the new field is classified.

The golden test at crates/cmtraceopen-parser/tests/intune_windows_remediations.rs detects this only if a fixture happens to carry sensitive content in the new field.

♻️ Proposed pattern for one helper
 fn redact_payload(payload: &RemediationPayload) -> RemediationPayload {
+    // Destructured, not spread: a new field must be classified here before
+    // this compiles.
+    let RemediationPayload {
+        evidence,
+        parsed,
+        raw_text,
+    } = payload;
     RemediationPayload {
-        raw_text: redact_classified(&payload.raw_text),
-        ..payload.clone()
+        evidence: evidence.clone(),
+        parsed: *parsed,
+        raw_text: redact_classified(raw_text),
     }
 }
🤖 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/apps/windows/remediations/redaction.rs`
around lines 30 - 56, Replace the functional updates using `..artifact.clone()`,
`..observation.clone()`, `..payload.clone()`, and `..transaction.clone()` in
`redact_artifact`, `redact_observation`, `redact_payload`, and
`redact_transaction` with exhaustive struct destructuring and reconstruction.
Preserve existing field values and redaction behavior while explicitly handling
every field, so adding a new classified field causes a compile-time failure
until it is masked.
🤖 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/apps/windows/common/redaction.rs`:
- Around line 69-77: Update command_line_re in the flag-to-value separator
pattern to accept whitespace, colon, or equals separators, ensuring values in
forms such as -Password:hunter2 and -Token=abc are redacted. Add regression
coverage through redact_text for both colon- and equals-separated credential
values while preserving existing whitespace-separated behavior.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs`:
- Around line 401-422: Update the DetectionState::Failed |
DetectionState::TimedOut handling in the evidence-request match to terminate
explicitly regardless of has_output_evidence. For failed or timed-out detection,
inspect report and request the appropriate reporting evidence only when
reporting is not observed; otherwise return None, preventing fallthrough to the
remediation-stage match.
- Around line 629-643: Update apply_detection at
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs:629-643
to preserve the existing state and exit_token when a StageCompleted record has
no readable code and the current state is Compliant or Noncompliant; otherwise
retain the current update behavior. Apply the same guard in apply_remediation at
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs:655-666
for Succeeded or ExitedNonZero, preserving the determined state and token when
the new completion lacks a readable code.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs`:
- Around line 72-81: Update extract_ids or a sibling script-path helper to
return the optional stage captured by script_path_re, and update extract_stage
to prefer that path-derived detect/remediate value before falling back to
message wording. Propagate the stage through classify_record and extend
script_path_yields_both_key_halves to verify the captured stage, preserving
existing ID extraction and fallback behavior.
- Around line 335-357: Update extract_payload to track whether the scan is
inside a JSON string and whether the current character is escaped, ignoring
braces while inside string literals. Preserve the existing depth-based
extraction and parsed flag for non-string braces, and add coverage for a payload
containing a closing brace inside a string.
- Around line 253-275: Update the hex-sourced branch of the exit-code parsing
logic to derive hex_text from the parsed signed value, matching the decimal
branch’s normalized width and sign representation. Only produce hex_text when
parsing succeeds; unreadable or oversized literals must leave it as None. Reuse
the existing signed-value-to-hex conversion used by the decimal branch rather
than preserving the original source digits.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs`:
- Around line 85-97: Case-sensitive suffix checks misclassify mixed-case Windows
artifacts. In
crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs lines
85-97, update output_artifact_key to use a shared case-insensitive
suffix-stripping helper for .output and .error; in lines 32-35, update the
log-artifact parsing path to use the same helper for .log. Define the helper
near split_rotation and add coverage for a mixed-case extension.

---

Nitpick comments:
In `@crates/cmtraceopen-parser/src/intune/apps/windows/remediations/redaction.rs`:
- Around line 44-49: Document in the RemediationPayload model or redact_payload
function that parsed records whether the original, pre-redaction raw_text was
valid JSON and is intentionally preserved unchanged after redaction. Keep the
existing parsed value while clarifying that it must not be interpreted as the
parse status of the redacted text.
- Around line 30-56: Replace the functional updates using `..artifact.clone()`,
`..observation.clone()`, `..payload.clone()`, and `..transaction.clone()` in
`redact_artifact`, `redact_observation`, `redact_payload`, and
`redact_transaction` with exhaustive struct destructuring and reconstruction.
Preserve existing field values and redaction behavior while explicitly handling
every field, so adding a new classified field causes a compile-time failure
until it is masked.

In `@crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs`:
- Around line 506-520: Rename the inner sort-key closure in the all_trustworthy
sorting block to avoid shadowing the surrounding key: RemediationKey parameter,
and update its call sites in the comparator. Keep the sorting behavior
unchanged.
- Around line 142-159: Update the comment above the conflicting_policy check to
distinguish the two behaviors: conflicting policies prevent keying the block,
while conflicting stages only prevent assigning a resolved stage and still allow
policy and run identifiers to be assigned. Keep the existing control flow
unchanged.
- Line 357: Limit the observations collected in the reducer around
`records.iter().map(to_observation)` to records relevant to remediation
analysis, using `classification.in_scope` or the presence of a key or signal,
while preserving all currently included in-scope records. Review and update
affected fixtures or expectations so they no longer depend on out-of-scope
unclassified entries.

In `@crates/cmtraceopen-parser/tests/intune_windows_remediations.rs`:
- Around line 319-345: Expand the scenario list in
exit_tokens_never_cross_stages to include same-minute-distinct-policies and
scheduled-and-on-demand-kept-separate, ensuring the detection-stage invariant is
exercised for these fixtures while preserving the existing assertions and
scenarios.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c459974-445e-4094-88a3-d103551e980d

📥 Commits

Reviewing files that changed from the base of the PR and between a207376 and eb033db.

⛔ Files ignored due to path filters (33)
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-agent-executor/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-health-scripts/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/HealthScripts-1.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/HealthScripts.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/AgentExecutor.log is excluded by !**/*.log
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/HealthScripts.log is excluded by !**/*.log
📒 Files selected for processing (47)
  • crates/cmtraceopen-parser/src/intune/apps/windows/common/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/common/redaction.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/models.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/redaction.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/reducer.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/remediations/sources.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/mod.rs
  • crates/cmtraceopen-parser/src/intune/apps/windows/scripts/redaction.rs
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/expected-full.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-compliant-remediation-skipped/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-launch-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/detection-timeout/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/execution-completes-report-fails/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/malformed-embedded-payload/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-agent-executor/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-agent-executor/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-health-scripts/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/missing-health-scripts/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/privacy-redaction/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-exits-nonzero/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-launch-failure/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-compliant/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-succeeds-post-state-noncompliant/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/remediation-timeout/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/retry-sequence/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/rotation-boundary/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/same-minute-distinct-policies/manifest.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/expected.json
  • crates/cmtraceopen-parser/tests/fixtures/intune/windows/remediations/scheduled-and-on-demand-kept-separate/manifest.json
  • crates/cmtraceopen-parser/tests/intune_windows_remediations.rs

Comment thread crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs Outdated
All nine findings from Copilot and CodeRabbit were valid.

1. An IME log whose remediation records all carry `component="HealthScripts"`
   was classified `Unknown` and never reached the reducer, because the
   file-confirmation list omitted the one component this workload actually
   uses there. Added.

2. A record naming an output path was treated as proof that the output artifact
   was supplied. It is not: it proves the script wrote one somewhere on the
   device. Using it to suppress the evidence request hid genuinely missing
   artifacts. Only an artifact actually present in the bundle counts now.

3. Credential values separated by `:` or `=` were exported verbatim.
   `-Password:hunter2` and `-Token=abc` are as common as the spaced form.

4. A detection that failed or timed out *with* output evidence fell through to
   the remediation branch and asked for remediation records -- contradicting the
   confidence rule in the same file, which treats a terminal detection as a
   complete story where remediation is legitimately absent. A terminal
   detection now always ends the request chain.

5. A later completion with no readable exit code erased a determined result.
   Both stage appliers overwrote state unconditionally and mapped a missing
   code to `InsufficientEvidence`, so a trailing generic "Detection script
   finished" discarded what a readable code had already proved. Such a record
   now contributes evidence without downgrading the state.

6. `script_path_re` captured `detect|remediate` and the captured group was
   thrown away. A path such as `...\{policy}_{run}\detect.ps1` therefore named
   no stage, because the prose rules require the word `script` to follow. The
   path states the stage as plainly as the prose does; it is now used.

7. `hex_text` was copied from the source text rather than rendered from the
   parsed value, so the same exit code rendered two ways (`0x1` against
   `0x00000001`), a negative hex code lost its sign, and an unreadable
   oversized literal still produced a hex form while `decimal` was `None`.

8. The payload brace scan counted braces inside JSON string literals, so
   `{"Detail":"a } b","Compliant":false}` was truncated to `{"Detail":"a }` and
   then reported as malformed -- losing the evidence the module exists to keep
   and misreporting coverage. String and escape state are now tracked.

9. Extension matching enumerated fixed casings, so `HealthScripts.Log` and
   `{policy}_{run}.Output` classified as `Unknown`. Windows file names are
   case-insensitive; one shared helper now handles both sites.

Findings 7 and 8 are the two most likely to bite in practice, because detection
scripts emit arbitrary text into payload fields and vendors write exit codes in
either base.

Three fixtures were added for the behaviour changes that no existing scenario
covered: `detection-timeout-with-retained-artifact` (finding 4),
`later-completion-without-readable-code` (finding 5), and the case-insensitive
and IME-confirmation cases as unit tests (findings 1 and 9).

Verified from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 446 unit, 25
  `intune_windows_remediations`, 24 scripts, 27 company-portal, 222 esp, 1 doc
  test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.
- `git diff --check`: clean.

Note for follow-up: finding 7 describes the same `hex_text` derivation that
exists in the already-merged `apps::windows::scripts::rules`. That module is not
touched here; it should get the same fix.

Refs #360

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 90 changed files in this pull request and generated no new comments.

Suppressed comments (1)

crates/cmtraceopen-parser/src/intune/apps/windows/remediations/rules.rs:368

  • extract_payload counts {/} bytes without accounting for JSON strings/escapes. This will incorrectly terminate payload capture when a string value contains { or } (valid JSON), causing well-formed embedded payloads to be truncated and reported as malformed (or missed). Consider tracking in_string/escape so brace depth only changes outside quoted strings.
        .and_then(|caps| caps.name("run").map(|m| m.as_str().to_ascii_lowercase()));
    (policy, run)
}

/// Extract a balanced top-level `{...}` span and report whether it parsed.

@adamgell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@adamgell
adamgell requested a review from Copilot July 31, 2026 18:22
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

`main` gained the parser-family skeleton for epic #356 (#388), which reserved a
slot at `intune::apps::windows::remediations` with a doc-only placeholder and
declared the sibling `microsoft_store` and `win32` slots alongside it.

Two conflicts, both in module declarations:

- `apps/windows/mod.rs`: took main's list of reserved slots and added `common`,
  the shared masking module this branch introduces.
- `remediations/mod.rs`: took this branch's implementation. The placeholder it
  replaces says "Implementation pending ... Add source classification,
  reduction, and findings submodules here", which is what this branch does.

Not done in this merge, and worth stating plainly: #388 also landed
`intune::evidence`, a shared contract carrying `IntuneTimestamp`,
`IntuneEvidenceRef`, `IntuneSensitivity`, `IntuneFindingConfidence` and the
observation-context envelope, and the placeholder asks leaves to consume it.
This module defines its own `RemediationTimestamp`, `RemediationEvidenceRef`,
`RemediationSensitivity` and `RemediationConfidence` instead, because it was
written before that contract existed.

Adopting it is the right end state but is deliberately not attempted here. The
already-merged `apps::windows::scripts` leaf is in exactly the same position for
the same reason, and migrating one sibling without the other would replace a
shared gap with an inconsistency between two leaves that a reader would then
have to reconcile. Both should move together, in a change whose diff is the
migration and nothing else.

Verified on the merged tree from the repository root:

- `cargo test --locked -p cmtraceopen-parser`: 453 unit, 25
  `intune_windows_remediations`, 24 scripts, 26 parser-family, 27
  company-portal, 222 esp, 1 doc test; 0 failed.
- `cargo clippy --locked -p cmtraceopen-parser --all-targets -- -D warnings`:
  clean.

Refs #360

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

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 90 changed files in this pull request and generated no new comments.

@adamgell
adamgell merged commit 1aa3258 into main Aug 1, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feature New feature intune Microsoft Intune related parser Log parser related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants