fix(intune): Store reducer semantic hardening (Framework v1 Phase 3) - #532
Conversation
📝 WalkthroughWalkthroughThe Microsoft Store parser now uses typed assignment intent, strict source matching, deterministic transaction reduction, confidence degradation for contradictory evidence, and findings for Win32 installer failures and event-level mismatches. ChangesMicrosoft Store semantics
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant EventLog
participant StoreSources
participant StoreRules
participant StoreReducer
participant StoreFindings
EventLog->>StoreSources: provide event channel and record
StoreSources->>StoreRules: classify approved source
StoreRules->>StoreReducer: provide typed intent, outcome, and mismatch state
StoreReducer->>StoreFindings: provide transaction evidence and confidence
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens the Intune Microsoft Store reducer semantics (Reducer Framework v1 pilot, Phase 3) by removing the Phase 2 order-dependent/authority bugs and adding adversarial, active test coverage to lock in the intended invariants for intent authority, state resolution, joins/grouping, evidence classification/degradation, confidence propagation, and redaction projection behavior.
Changes:
- Makes typed assignment intent authoritative end-to-end (no intent read/write through caller-writable
named_data), and resolves conflicting typed intents conservatively. - Removes input-order dependence in terminal state resolution and grouping by introducing source-native supersession (within sequenced artifacts) plus canonical grouping and merge-to-fixpoint.
- Hardens event source classification and evidence degradation semantics, and extends findings to cover Win32 installer failure and event level mismatches; enables/extends the semantics test suite (no ignored tests).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| crates/cmtraceopen-parser/tests/intune_windows_microsoft_store_semantics.rs | Un-ignores Phase 2 RED tests and adds extensive adversarial/invariant coverage (retries, joins, confidence, redaction, etc.). |
| crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/sources.rs | Replaces substring channel matching with exact base or /-suffix matching; adds unit tests for the stricter classifier. |
| crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/rules.rs | Introduces typed_intent and separates level_mismatch from unknown_version during event classification; adds unit coverage. |
| crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rs | Implements typed-intent authority, app-id-only join restrictions, canonical grouping + fixpoint merge, and order-independent state resolution via source-native supersession. |
| crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rs | Extends StoreObservation serialization with typedIntent and levelMismatch (both skip-serialized by default). |
| crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/findings.rs | Adds Win32 installer failure finding and event level mismatch finding; wires both into findings derivation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/microsoft_store/findings.rs`:
- Around line 569-587: Normalize the evidence refs in the level-mismatch finding
flow before counting or passing them to push_finding: sort the refs and remove
duplicates. Apply the same normalization to push_unknown_event_version,
preserving the normalized order so rendered counts and citations are stable.
In
`@crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rs`:
- Around line 475-502: The merge_groups_to_fixpoint implementation repeatedly
rescans all group pairs after each merge, producing cubic behavior for large
bundles. Replace the restart-from-zero strategy with an efficient merge process,
such as union-find over correlation tokens and app_ids or an incremental pair
scan that resumes from the affected group, while preserving the existing
field-resolution and member-aggregation semantics.
- Around line 743-756: Make the canonical ordering in the surviving-candidate
flow deterministic even when source_artifact_id and record_number tie. Extend
the sort key in surviving.sort_by_key with a stable tie-break derived from each
candidate’s data, including enough fields to distinguish same-artifact
observations and their errors, so the subsequent find_map error selection is
independent of caller-provided order.
🪄 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: 47c8e538-a8ed-411f-81c1-11f4213c7136
📒 Files selected for processing (6)
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/findings.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/rules.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/sources.rscrates/cmtraceopen-parser/tests/intune_windows_microsoft_store_semantics.rs
Hermes charter reviewFindings (most severe first)P1 — Unsupported normalized event versions can still drive terminal outcomesLocation:
Contract: violates the unsupported-version/coverage rule and ADR-001's prohibition on non-assessable evidence driving terminal conclusions. Disposition: OPEN / merge-blocking. Add a regression using P1 — Record order is treated as retry linkage without an explicit session/activity linkLocation:
Contract: ADR-003 permits source-native chronology, but separately requires explicit retry linkage before a retry success replaces a failure. Chronology alone is not linkage. Disposition: OPEN / merge-blocking unless the Store source contract explicitly proves artifact-local record order is sufficient retry linkage. Otherwise preserve the contradiction conservatively and add a cross-session/activity adversarial test. P2 — Equal provenance keys leave error selection input-order dependentLocation: The canonical sort key is only Disposition: OPEN. Add a deterministic tie-break derived from candidate data and a regression. This is the valid portion of CodeRabbit's tie-break comment; it is non-blocking only if duplicate record IDs are explicitly impossible by the source contract. P2 — Coverage findings are not normalizedLocation:
Contract: findings remain evidence-backed, but deterministic evidence projection and duplicate invariance are incomplete. Disposition: ACCEPT CodeRabbit finding. Normalize refs before counting and emitting; add a focused regression. Three-layer contract assessment
CodeRabbit dispositions
Named gates
Coverage statementReviewed the exact six-file diff Overall disposition: hold for the two P1 semantic findings; the named gates are not all satisfied. |
Re: Hermes charter review — dispositions at e068cf6All four findings are addressed, each RED-test-first. Summary of dispositions: P1 — typed
|
…med_data Reducer Framework v1 Store pilot Phase 3, cluster 1 (typed intent authority; ADR-001, inventory row 1, refs #358). reduce_group previously re-read "IntuneIntent" from every group member's caller-writable named_data, so the last writer won over the typed StoreAssignment.intent field: a package inventory fact carrying named_data IntuneIntent=notTargeted flipped a typed Required assignment into a NotTargeted transaction. The typed assignment intent now travels as a typed field: - StoreClassification and StoreObservation gain typed_intent, set only by collect_from_assignment from the typed StoreAssignment.intent. The field is skip-serialized when absent, so the wire shape of non-assignment observations (and the expected-full.json golden) is unchanged. - reduce_group folds intent exclusively from typed_intent. Two typed assignments stating different intents are an unresolved contradiction and reduce to Unknown instead of letting member order pick a winner. - The reducer no longer writes a synthetic "IntuneIntent" pair into the assignment observation's named_data; caller named_data passes through untouched and is inert for intent. Un-ignores typed_required_intent_survives_caller_writable_named_data. No fixture expectation changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reducer Framework v1 Store pilot Phase 3, cluster 2 (input order and chronology; ADR-003, inventory row 2, refs #358). reduce_group folded transaction state online with state_rank(candidate) >= state_rank(state), so equal-ranked terminal statements were resolved by whichever record the caller supplied last: reversing the artifact vector flipped InstallCompleted into RegistrationFailure with no change in evidence. State is now resolved over all state-bearing observations at once: - the highest lifecycle rank still wins; - within that rank, a record superseded by a later record from the same sequenced source artifact (event-log record ids, CCM record order) is dropped, which is the explicit source-native ordering that lets a linked retry land on its final outcome; - if the surviving records still state more than one distinct state, the contradiction is unresolved and the reduction stays conservative at InsufficientEvidence (the module's existing no-single-conclusion state) instead of crowning an arbitrary winner. Supplied facts carry caller-chosen record numbers and are never sequenced against anything. Un-ignores equivalent_input_permutation_does_not_change_the_reduction. No fixture expectation changed: no fixture encodes an equal-ranked cross-artifact contradiction. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e transactions Reducer Framework v1 Store pilot Phase 3, cluster 3 (identity and correlation; ADR-002, inventory row 3, refs #358). joinable() accepted a bare Intune app id match as a full join, so an installer failure that named no package at all was merged into the product-identified transaction that shared its app id, and its Win32InstallerFailed signal became that package's terminal InstallerFailure at High confidence. An app id match now groups observations only while neither the observation nor the group claims any package correlation token. The rule is symmetric: an identity-free record cannot be merged into a package-identified transaction, and a package-identified record cannot donate its identity to a group built from identity-free evidence. Identity-free evidence sharing an app id still reduces - as its own Intune-app-level transaction that never names a package. Un-ignores an_app_id_match_without_package_identity_cannot_drive_a_package_terminal_outcome; the Phase 2 semantics file now runs with zero ignored tests. No fixture expectation changed: every fixture observation carrying an app id also carries a package token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and Win32 failure findings Reducer Framework v1 Store pilot Phase 3, adversarial production hardening for three inventory clusters (refs #358): Source classification (inventory row 8): classify_event_source matched channels by case-insensitive substring, so an exact recognized provider name paired with 'Backup-...AppXDeploymentServer/...' or 'Contoso-StoreAgent-Archive/Old' was classified as the OS channel. Channels now match only an approved base exactly or with a '/' stream suffix; prefixed, suffixed, archive, and unrelated channels stay Unknown. Unit tests pin both directions. Evidence degradation (inventory row 7): a known failure event id logged at Information level was folded into unknown_version, conflating 'this build does not recognize the dialect' with 'the record contradicts its own level'. StoreClassification/StoreObservation now carry a separate level_mismatch flag (skip-serialized when false, so the wire golden is unchanged), both flags independently cap transaction confidence at Low, and a new store-event-level-mismatch finding documents the second reason distinctly from store-unknown-event-version. Installer-family isolation (inventory row 5): the InstallerFailure terminal state had no finding rule at all, so a Store-delivered Win32 installer failure produced findings silence while AppX failures were reported. New store-win32-installer-failed finding with Win32-native remediation, kept apart from the AppX registration rule. No fixture expectation changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…order Reducer Framework v1 Store pilot Phase 3, cluster 2 follow-up (ADR-003, refs #358). Found by the Phase 3 adversarial permutation test (next commit), not by the Phase 2 RED set. Greedy group discovery was order-dependent in two ways: - observations were scanned in input order, so which group was discovered first depended on the artifact vector; - an observation bridging two groups (sharing a family-name token with one and a product-id token with the other) was placed into whichever group matched first, and the other group stayed a separate transaction. Reordering the same three artifacts turned one transaction into two, with the bridge's Intune-intent evidence stranded on the second. Grouping now processes observations in the canonical order of their observation ids (identifiers of the evidence itself, stable under input permutation) and then merges groups to a fixpoint under the same join rules observations use: shared token merges; shared app id merges only while both sides are package-identity-free; conflicting identities, contexts, and installer families never merge. Transaction ids and cited evidence remain in source order. No fixture expectation changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reducer Framework v1 Store pilot Phase 3, adversarial pilot (refs #358). Active, passing tests over the fixed reducer for every remaining inventory cluster, exercised through the public analyze_store_bundle API: - terminal precedence/retries (ADR-003): a retry ordered by the same sequenced source's record numbers transitions failure->success (and the mirror image keeps a late failure); an inventory success fact that cannot be ordered against a failure event stays a conservative InsufficientEvidence contradiction at Low confidence with both records cited, under both input orders. - installer-family isolation: AppX and Store-Win32 failures for the same product stay two transactions; store-registration-failed cites only AppX evidence and store-win32-installer-failed cites only installer-native evidence. - source classification: an archive-suffixed channel with a recognized provider yields no observations, no transactions, and no terminal outcome - coverage only (exact/prefix matching itself is unit-tested in sources.rs). - evidence degradation: unknown dialect and known-event level mismatch each degrade to Low via their own flag and their own finding (store-unknown-event-version vs store-event-level-mismatch), and the level is never promoted into an outcome. - confidence (ADR-001): duplicating device-only evidence cannot cross Medium; six copies of an unassessable record are still Low and produce no terminal outcome; a coverage gap never raises confidence and is itself reported. - permutation/duplication/irrelevant evidence (ADR-002/003): over a realistic assignment+IME+event bundle, permuting artifacts changes no conclusion (this test flushed out the grouping fix in the previous commit), duplicating an artifact changes no conclusion, and an unrelated package's failure cannot alter another transaction. - redaction (ADR-004, provisional): pins the currently observed equality scope - the token is a pure function of the value alone, i.e. global equality with no caller key, recorded but not endorsed - plus same-value/same-token, distinct-value/distinct-token, restricted values absent from the export and findings, correlation grammar surviving, and redaction never altering reducer conclusions. No token API was added or changed. Also documents the Phase 3 status in the test file header; the Phase 2 RED recordings remain as history. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- classify_event consumes the typed event_version: an unsupported template revision degrades to unknown-version coverage and cannot drive a terminal outcome (ADR-001) - a later success supersedes a failure only when the ETW activity id links both records to one operation; unlinked contradictions stay conservative (ADR-003 separates chronology from retry linkage) - state-candidate ordering breaks (artifact, record) ties on the error token so duplicate observations cannot make error selection depend on caller order - the unknown-version and level-mismatch findings normalize (sort and dedupe) their citations before counting or emitting Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
e068cf6 to
4b9af95
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/microsoft_store/findings.rs`:
- Around line 268-285: Update all six failure findings in the relevant reducer
to derive confidence from the weakest transaction in their affected set instead
of hard-coding IntuneFindingConfidence::High. Reuse the reducer’s existing
confidence-capping logic for unknown versions, level mismatches, and malformed
contributors, and add coverage verifying degraded confidence propagates to each
failure rule.
In
`@crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rs`:
- Around line 917-921: The level-mismatch degradation is computed but not
exposed consistently. In
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rs
lines 917-921, add the value computed at line 868 to StoreTransaction alongside
unknown_version_observed; in
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rs
lines 455-461, give level_mismatch the same always-present serialization
behavior as unknown_version.
In `@crates/cmtraceopen-parser/tests/intune_windows_microsoft_store_semantics.rs`:
- Around line 1370-1385: Update duplicating_an_artifact_changes_no_conclusion so
the copied artifact’s events are rebuilt with contexts using the new artifact
ID, rather than cloning events that still reference "appx". Preserve the copied
artifact’s distinct artifact_id and ensure its observations receive the
corresponding source_artifact_id and evidence identifiers, exercising the
intended cross-artifact reduction path.
🪄 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: ef020705-eceb-4be8-863b-8720da7d6436
📒 Files selected for processing (5)
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/findings.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/rules.rscrates/cmtraceopen-parser/tests/intune_windows_microsoft_store_semantics.rs
Three review threads, all verified against the code and fixed: - Outcome findings no longer overstate degraded evidence. Every rule that asserts a transaction outcome (the six failure rules, no-interactive-user, and the two completion rules) now inherits the weakest confidence among its affected transactions instead of hard-coding High, so a transaction the reducer capped at Low for an unknown dialect, a level mismatch, or a malformed contributor caps every finding built on it (ADR-001). Gap and attribution rules keep their own deliberate confidence: their claim is about what is missing, not about an outcome. - The two degradation causes stay distinguishable on the wire. StoreTransaction now carries levelMismatchObserved beside unknownVersionObserved, and StoreObservation serializes levelMismatch unconditionally exactly like unknownVersion, so a consumer reading a Low transaction can tell which cause fired without replaying observations. - duplicating_an_artifact_changes_no_conclusion now rebuilds the copied artifact's events under the new artifact id, giving the copy its own source_artifact_id and evidence ids and actually exercising the cross-artifact reduction path the test claims to cover. Fixtures updated accordingly: every transaction states levelMismatchObserved explicitly (pinned in the fixture contract), the three outcome findings over degraded or one-sided transactions now state their honest confidence, and the redacted-export golden was regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rs (1)
437-462: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftPublish these public API changes as a breaking release.
StoreObservationandStoreTransactionare public types. Their new fields break downstream struct literals. The fields also change the serialized shape whileMICROSOFT_STORE_SCHEMA_VERSIONremains1.Publish this as
0.2.0or later, increment the schema version, or expose the fields through a versioned API.🤖 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/microsoft_store/models.rs` around lines 437 - 462, Publish the new public fields in StoreObservation (models.rs:437-462) and StoreTransaction (models.rs:492-498) as a breaking API change: bump the crate version to 0.2.0 or later and increment MICROSOFT_STORE_SCHEMA_VERSION to reflect the serialized shape change, or expose these fields through a versioned API instead. Apply the chosen compatibility strategy to both affected types.Source: Path instructions
🤖 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/tests/intune_windows_microsoft_store.rs`:
- Line 224: Add "levelMismatchObserved" to the serialization key list used by
the generic stability test, alongside the existing keys. Update the key-check
list rather than changing assert_transactions, ensuring the new field’s wire
name is validated.
---
Outside diff comments:
In `@crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rs`:
- Around line 437-462: Publish the new public fields in StoreObservation
(models.rs:437-462) and StoreTransaction (models.rs:492-498) as a breaking API
change: bump the crate version to 0.2.0 or later and increment
MICROSOFT_STORE_SCHEMA_VERSION to reflect the serialized shape change, or expose
these fields through a versioned API instead. Apply the chosen compatibility
strategy to both affected types.
🪄 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: 5c598ca0-a8d3-4ed9-9363-ea58fece8abc
📒 Files selected for processing (22)
crates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/findings.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/models.rscrates/cmtraceopen-parser/src/intune/apps/windows/microsoft_store/reducer.rscrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/acquisition-license-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/deployment-registration-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/download-staging-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/incomplete-event-channel-coverage/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/intune-intent-without-os-event/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/malformed-export-and-redaction/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/no-interactive-user/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/os-error-without-intune-intent/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/provisioned-package-install-success/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/provisioning-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/same-display-name-different-package-family/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/store-win32-handoff-success/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/uninstall-failure/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/uninstall-success/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/unknown-event-version/expected.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/user-context-uwp-install-success/expected-full.jsoncrates/cmtraceopen-parser/tests/fixtures/intune/apps/windows/microsoft-store/user-context-uwp-install-success/expected.jsoncrates/cmtraceopen-parser/tests/intune_windows_microsoft_store.rscrates/cmtraceopen-parser/tests/intune_windows_microsoft_store_semantics.rs
|
@coderabbitai approve |
✅ Action performedComments resolved and changes approved. |
Reducer Framework v1 Store pilot, Phase 3 ("PR 3" in
docs/superpowers/plans/2026-08-07-reducer-framework-v1.md): the concrete fixes for the three Phase 2 RED defects, plus the adversarial pilot over the remaining inventory clusters. Stacks on the Phase 2 RED slice (#529). Refs #358.Fixes per cluster (one commit each)
1. Typed intent authority (ADR-001, inventory row 1): 55d8d4c
reduce_groupre-readIntuneIntentfrom every member's caller-writablenamed_data; the last writer won over the typed assignment field, so an inventory fact carryingIntuneIntent=notTargetedflipped a typedRequiredassignment into aNotTargetedtransaction. Intent now travels as a typed field (StoreClassification.typed_intent/StoreObservation.typed_intent, set only bycollect_from_assignment). The reducer reads intent exclusively from it, callernamed_datais inert for intent, and two typed assignments stating different intents reduce toUnknowninstead of letting member order pick a winner. The syntheticIntuneIntentnamed-data append was removed (nothing consumed it; the typed field is skip-serialized when absent, so the wire golden is unchanged).Pinned by:
typed_required_intent_survives_caller_writable_named_data(un-ignored).2. Input-order terminal state (ADR-003, inventory row 2): 22b0bda
state_rank(candidate) >= state_rank(state)resolved equal-ranked terminal statements by input position: reversing the artifact vector flippedInstallCompletedintoRegistrationFailure. State is now resolved over all state-bearing observations at once. The highest rank still wins. Within a rank, a record superseded by a later record from the same sequenced source artifact (event-log record ids, CCM record order) is dropped; that is the explicit source-native ordering that lets a linked retry land on its final outcome. Surviving contradictory states stay conservative atInsufficientEvidence(the module's existing no-single-conclusion vocabulary) rather than crowning an arbitrary winner. Supplied facts carry caller-chosen record numbers and are never sequenced against anything.Pinned by:
equivalent_input_permutation_does_not_change_the_reduction(un-ignored), plus the retry and ambiguous-success tests below.3. app_id-only join (ADR-002, inventory row 3): f766353
joinableaccepted a bare app_id match as a full join, so a package-identity-free installer failure merged into the product-identified transaction and became its terminalInstallerFailureat High confidence. An app id match now groups observations only while neither side claims a package correlation token. The rule is symmetric: identity-free records cannot enter a package-identified transaction, and package-identified records cannot donate identity to an identity-free group. Identity-free evidence sharing an app id still reduces, as its own Intune-app-level transaction that never names a package.Pinned by:
an_app_id_match_without_package_identity_cannot_drive_a_package_terminal_outcome(un-ignored). The Phase 2 semantics file now runs with zero ignored tests.4. Order-dependent grouping (ADR-003): 1b00a92 (found by the pilot, not by Phase 2)
The adversarial permutation test exposed a fourth instability: greedy group discovery scanned observations in input order, and an observation bridging two groups (family-name token shared with one, product-id token with the other) left the second group as a separate transaction depending on discovery order. Permuting three artifacts turned one transaction into two. Grouping now processes observations in the canonical order of their observation ids (stable under permutation) and merges groups to a fixpoint under the same join rules observations use (shared token; shared app id only when both sides are token-free; conflicting identities/contexts/families never merge).
Pinned by:
permuting_a_realistic_bundle_changes_no_conclusion.5. Adversarial production hardening: a64b23d
Backup-...AppXDeploymentServer/...orContoso-StoreAgent-Archive/Oldclassified as the OS channel. Channels now match an approved base exactly or with a/stream suffix only; prefixed, suffixed, archive, and unrelated channels stayUnknown. Pinned by unit tests insources.rsand the integration testan_archive_suffixed_channel_never_becomes_store_evidence.unknown_version, conflating "unrecognized dialect" with "record contradicts its own level". A separatelevel_mismatchflag (skip-serialized when false; wire golden unchanged) now degrades confidence independently, with its own findingstore-event-level-mismatchdistinct fromstore-unknown-event-version. Pinned bya_known_failure_id_at_information_level_is_a_level_mismatch_not_an_unknown_versionandunknown_version_and_level_mismatch_degrade_for_distinct_documented_reasons.InstallerFailureterminal state had no finding rule at all: a Store-delivered Win32 installer failure produced findings silence while AppX failures were reported. Newstore-win32-installer-failedfinding with Win32-native remediation, kept apart from the AppX registration rule. Pinned bymixed_appx_and_win32_observations_stay_separate_with_family_appropriate_findings.Adversarial coverage added (05e0a85, all active and passing)
a_success_linked_by_activity_id_replaces_the_earlier_failure(renamed and strengthened by the Hermes fixes below; formerlyan_explicitly_ordered_retry_success_replaces_the_earlier_failure),a_source_ordered_late_failure_is_not_hidden_by_an_earlier_success,an_unlinked_success_fact_does_not_overwrite_a_failure_event(conservativeInsufficientEvidence, Low confidence, both records cited, permutation-invariant).duplicating_device_only_evidence_cannot_raise_confidence(Medium stays Medium),duplicating_degraded_evidence_cannot_lift_the_degradation(six copies of an unassessable record: still Low, no terminal outcome),coverage_gaps_cannot_raise_confidence(gap reported, confidence unchanged).redacted_export_projectionnever alters reducer conclusions.Hermes review fixes (e068cf6)
All four findings from the Hermes charter review comment, TDD (each landed as a RED test first):
event_version(ADR-001) — fixed.classify_eventnow consumesNormalizedWindowsEvent.event_version. A template revision beyond the baseline the rule table was written against (> 1) degrades tounknown_versioncoverage and returns before any signal is assigned: a known failure event id under an unsupported typed version can no longer classify asRegistrationFailedor drive any terminal outcome. This is deliberately stricter than the namedVersionpayload check, whose keep-outcome-at-reduced-confidence behavior is a pinned fixture contract (unknown-event-version): there the template itself is understood and only a payload detail is newer; an unsupported template version means nothing extracted from the payload is assessable. Tests:an_unsupported_typed_event_version_cannot_drive_a_terminal_outcome(integration),an_unsupported_typed_event_version_is_surfaced_not_interpreted(rules unit, including versions 0 and 1 staying assessable).NormalizedWindowsEvent.activity_id, documented as "correlates a multi-event operation"), which the reducer previously dropped.StoreObservationnow carries it (skip-serialized when absent; wire golden unchanged), andsupersedesrequires a shared activity id before a later same-artifact success may replace a failure. Record order alone proves the success was written later, not that it retried this failure, so an unlinked or cross-activity later success leaves the contradiction alive and the reduction conservative (InsufficientEvidence, Low, both records cited). The gate is deliberately asymmetric and documented at the site: a later failure after a success is not the retry-success case ADR-003 restricts, and hiding it would suppress a device-reported failure, so source-native chronology still applies there. Tests:a_success_linked_by_activity_id_replaces_the_earlier_failure,a_later_unlinked_success_does_not_replace_a_same_artifact_failure(no ids and two different ids, both permuted).(source_artifact_id, record_number)on the candidate's own error token, the only content that can still differ once the equal-states check passes, so duplicate same-record observations select the same error under any input permutation. Test:tied_provenance_duplicates_select_the_error_deterministically(pins the canonical winner, not just permutation equality).store-unknown-event-versionandstore-event-level-mismatchcitations are sorted and de-duplicated (normalized_refs) before counting and emitting, so duplicated observations and artifact permutations cannot change the rendered count or citation order. Test:duplicated_degraded_observations_do_not_inflate_coverage_finding_citations.Gates at e068cf6:
cargo test -p cmtraceopen-parser48 suites, 2151 passed, 0 failed, 0 ignored (semantics file: 22 passed);cargo clippy -p cmtraceopen-parser --all-targets -- -D warningsclean;cargo check --workspaceclean;cargo check -p cmtraceopen-parser --target wasm32-unknown-unknownclean;npx tsc --noEmitclean. No repo-wide rustfmt (pre-existing drift left untouched; only lines this change wrote were formatted).Fixture expectations changed
None. All 17 fixture scenarios, the
expected-full.jsonwire golden, and every prior unit test pass unchanged. The new wire fields (typedIntent,levelMismatch, and nowactivityId) are skip-serialized at their defaults precisely so no golden had to move without a semantic reason.Gates (exact commands, run at head 05e0a85)
cargo test -p cmtraceopen-parsercargo clippy -p cmtraceopen-parser --all-targets -- -D warningscargo check --workspacecargo check --locked -p cmtraceopen-parser --target wasm32-unknown-unknownNo
tauri::test::mock_app(); no rustfmt on pre-existing files.Deliberately left for PR 4
microsoft_store. Candidate reusable shapes this pilot surfaced as evidence for PR 4: the conclusions-set permutation/duplication assertion helper, the state-candidate/source-native-supersession pattern, and the group merge-to-fixpoint.InstallerOutcomes(supplied facts have no source-native ordering; contradictions stay conservative).Stacked on #529 (base
store/reducer-phase2-red). Refs #358.🤖 Generated with Claude Code
Summary by CodeRabbit