# Roadmap Intune Preflight is at **v2.1**. The core is complete: endpoint simulation, assignment resolution (include/exclude, filters, inherited All Devices / All Users), the Autopilot V1/V2 enrollment layer, the Windows merged CSP baseline with human-readable Settings Catalog **and legacy Endpoint Security** names and values, **cross-model "legacy policy collision" detection**, the **"Legacy template" migration flag**, honest **Group Tag** surfacing in the picker, a tightly integrated Assignment Manifest (send groups to the simulator, per-group "seating charts"), and a **public, zero-backend demo**. This page tracks the **honest edges that remain** and the work planned to close them. None of the known limitations block the core use — they're documented so you know exactly where to double-check against Entra and the Intune admin center. See [CHANGELOG.md](CHANGELOG.md) for what shipped when. **Where v2 stands.** v2 is feature-complete and intended to stay that way. What remains for the 2.x line is **maintenance** — accuracy, correctness and polish within the capabilities already shipped, which is what [v2 maintenance](#v2-maintenance) lists. The bigger swings that would change what the tool *is* are collected under [v3 bets](#v3-bets); they are deliberately not 2.x work, and none of them is a prerequisite for trusting what v2 already does. If you rely on this tool, 2.x should feel boring. ## Known limitations ### Dynamic-group rule evaluation is flat / best-effort The **Autopilot** and **Group Tag** auto-selection (and the "implied group" detection) parse only *flat* membership-rule clauses: - single-level `or` / `and` of `device.devicePhysicalIds` clauses, - targeting the `[OrderID]` (Group Tag) and `[ZTDId]` (Autopilot) tags, - with `-eq` / `-startsWith` operators. They do **not** fully evaluate **combined / nested dynamic-group rules** — e.g. rules that nest parentheses, mix device properties (`(device.devicePhysicalIds -any (_ -startsWith "[OrderID]:X")) and (device.deviceOSType -eq "Windows")`), or use other operators (`-match`, `-in`, `-notIn`). Such a group may be missed by the auto-selection. Implied/auto-selected groups are always surfaced in the UI so they can be eyeballed against Entra. Two deliberate exceptions where the structure *is* understood: a bare `[ZTDId]` clause standing as a top-level `or` branch selects the group (Entra binds `and` tighter than `or`, so that branch guarantees membership on its own), and **implied-group detection is restricted to rules built solely from `devicePhysicalIds` clauses** — the moment either rule mixes in another property, no implication is claimed rather than guessing from a shared condition. **Surfaced, not guessed (shipped).** Rather than fake a full evaluator, the group picker now classifies each dynamic group against the entered Group Tag: a confident `[OrderID]` match is badged **group tag** and auto-selected; a match whose rule *also* hinges on a property a tag can't decide (deviceOSType, deviceCategory, …) is badged **+ conditions** with the membership rule shown to verify; and a tag that's referenced but can't be confirmed by the flat evaluator is badged **conditional** (surfaced, not auto-selected). The list sorts selected → matches → conditional → rest. So the honest edge is *visible* per group now, and a real rule engine (below) becomes an accuracy upgrade, not a correctness prerequisite. ### Conflict & overlap detection is Windows-only Value-level conflict and overlap detection is trustworthy for Windows. Other platforms don't map cleanly onto it yet: macOS `.mobileconfig` profiles share metadata that reads as false overlaps, and platform-specific frameworks — e.g. the common pattern of splitting one concern per macOS **compliance** policy — produce false conflicts. Both conflicts and overlaps are therefore suppressed off Windows. The **merged baseline** (every applied setting) is still shown on all platforms; only the conflict/overlap flags are Windows-only. ### Cross-model detection is best-effort (common settings only) Same-model conflict/overlap detection keys each setting on a model-specific id, so a **legacy template** and a **Settings Catalog** policy configuring the *same* underlying CSP used to slip past it entirely (`0 conflicts`, even though the device gets both). That's now covered by a **cross-MODEL** pass: settings resolve a canonical `cspNode` — free for Settings Catalog and OMA-URI (they carry a real CSP path), and via a curated template→CSP crosswalk for legacy device-config templates — and any CSP configured through two different models is surfaced as a cross-model finding (duplicate / conflict / verify), with the migrate-to-Settings- Catalog nudge. The honest edge: it's **best-effort over common settings**. Settings Catalog ↔ OMA-URI is exact; the template crosswalk is a small, hand-verified set (grounded in Microsoft's Policy CSP docs), not every setting. Legacy Endpoint Security intents aren't crosswalked yet (their definitions carry no CSP path). Expanding crosswalk coverage is the v2 item below; migrating legacy templates to the Settings Catalog remains the real fix, which the flag encourages. ### Legacy Endpoint Security intents: read, named, and value-labelled Legacy Endpoint Security & Security Baselines in the `deviceManagement/intents` object model (BitLocker / Disk Encryption, Defender Antivirus, Firewall, ASR, …) are read, merged into the baseline, and — being Windows — compared for conflicts/overlaps against other intents. Their **real setting names and enum value labels** are resolved from the template `settingDefinitions` (matching the Settings Catalog), and modern policies are never mislabeled "(Legacy)". The one remaining edge — a legacy intent vs a modern Settings Catalog policy setting the *same* thing — is the cross-model limitation above. ### Legacy device-config default noise — resolved Older template profiles (device restrictions, endpoint protection, compliance) serialize their *entire* fixed schema, defaulting untouched fields. Those unset defaults — `false` toggles plus the `notConfigured` / `userDefined` / `deviceDefault` / `unavailable` enum sentinels — are now dropped for those template types, so a profile contributes only what it actually configures (a Device Restrictions profile that blocks 9 things reports 9, not ~200). One residual: a real enum default (e.g. `cellularData = allowed`) can't be told from a deliberate choice without a per-property default map, so it may still show. Types where a `false` is genuinely meaningful (e.g. Update rings' `allowWindows11Upgrade`) are deliberately left untouched. ### No specific-user modelling Assignments to Entra *user* groups resolve normally — user groups are selectable like any other, and the **All Users** virtual group always applies. What's absent is any notion of a **specific user**: the tool never evaluates whether a named user belongs to a group, so nothing is inferred from a sign-in. Autopilot V2 (a user-driven flow) is therefore resolved by its configured just-in-time device group rather than its user assignment. ## v2 maintenance Work that makes what v2 already claims more accurate or more pleasant, without adding a capability. This is the whole intended scope of the 2.x line. - **Expand the cross-model crosswalk** — the best-shaped maintenance item there is: purely additive data, each entry independently testable, no architectural risk. Cross-model detection shipped best-effort (Settings Catalog ↔ OMA-URI by canonical CSP path, plus a curated template→CSP crosswalk in `packages/engine/src/crosswalk.ts`); growing it from Microsoft's migration-mapping docs directly widens coverage. Legacy Endpoint Security intents remain the harder half — their definitions carry no CSP path, so they need a mapping of their own. - **Cross-platform conflict & overlap accuracy** — per-platform handling so macOS/iOS/Android conflicts and overlaps are accurate rather than suppressed (including framework-aware compliance handling, e.g. the one-concern-per-macOS-compliance-policy pattern). Today the merged baseline is shown on every platform and only the flags are Windows-scoped; this narrows that gap without changing what the tool does. - **Per-property template defaults** — the last of the legacy default-noise edge: a real enum default (`cellularData = allowed`) still can't be told from a deliberate choice without a per-property default map. - **Autopilot groups by `enrollmentProfileName`** — a real-tenant targeting shape the flat evaluator doesn't recognise yet, alongside `[ZTDId]` / `[OrderID]`. - **Accessibility pass on the diagram and the baseline grid** — the panel and the dialogs are keyboard-navigable; the React Flow canvas and the resizable data grid are the parts that still assume a mouse. ## v3 bets Bigger swings. Each would change what the tool is capable of, carries real design risk, and is **deliberately not 2.x work** — v2's honesty about its edges is what makes them upgrades rather than corrections. - **Combined/nested dynamic-group rule evaluation** — the headline bet. Replace the regex-based clause extraction (`parsePhysicalIdClauses`, `groupTagMatchesRule`, `isAutopilotJoinedRule` in `packages/shared/src/index.ts`; `ruleImplies` in `packages/engine/src/normalize.ts`) with a real membership-rule expression parser and evaluator (tokenize → AST → evaluate against the simulated device's attributes). It is the single largest piece of new engine surface on this page. Explicitly **deferred**, and it's the "surfaced, not guessed" work that earned the right to defer it: because the picker now classifies each dynamic group against the entered tag (**match** / **+ conditions** / **conditional**, with the rule shown), the flat evaluator's limits are *visible per group* rather than silently wrong. A real engine is therefore an accuracy upgrade, not a correctness prerequisite. - **Richer user / user-group modelling** — assignments to Entra *user* groups resolve normally today (user groups are selectable, and All Users always applies), but there is no notion of a **specific user**: the tool never evaluates whether a named user belongs to a group. Modelling a signed-in user would let user-driven flows — Autopilot V2 in particular, which currently resolves by its configured device group — be simulated from the user side, and would enable user-scoped vs device-scoped setting context. - **Optional delegated (user sign-in) auth** — a device-code sign-in flow as an *alternative* to the long-lived client secret. Deliberate tradeoff: a delegated token is scoped to the signed-in admin's RBAC (scope tags / admin units), so it shows only *that admin's slice* — which narrows the tool's "full, unfiltered preflight" value. App-only stays the default and recommended mode. - **Tenant-wide policy ↔ group map** — a connective "nebula" of which policies bridge which groups across the whole tenant. The per-group seating chart (v1.2) is its single-group view; this is the zoomed-out whole-tenant version. ### Considered and parked Written down so the reasoning isn't relitigated, not as a queue. - **Effective-value resolution** ("which value wins for a conflicted setting"). Parked on **honesty grounds**, not effort. Intune's real resolution across policy models is genuinely underdefined, so a computed "winner" would be a guess wearing a confident face — the one thing this tool tries never to do. The honest version of the feature shipped instead in v2.1: a conflicted merged row is flagged `conflicted` and points at the full disagreement rather than implying its own value won (see [docs/SETUP.md](docs/SETUP.md#reading-a-conflicted-setting-from-the-api)). Revisit only if Microsoft documents the resolution order. - **Group-collision analysis** — surfacing the group *pairs* whose union produces conflicting settings (a device in both A and B). It leans on co-membership reasoning the tool avoids, and Intune already flags device-level conflicts — revisit only if the descriptive views prove it's wanted. ## Shipped since v1.0 **v2.1** — hardening, not features. The engine became its own package with its I/O-free boundary enforced three ways; the simulator's state became one tested reducer; a conflicted setting is now flagged rather than reading as an effective value; and a quality pass fixed a blocked-`localStorage` crash, a Graph body read that escaped its timeout, a mislabelled group in the Manifest and a request-size ceiling. Real linting, an error boundary, reproducible Docker images, an automated demo deploy, and 114 → 178 tests. See [CHANGELOG.md](CHANGELOG.md#210--2026-09-23). **v2.0** - **Cross-model "legacy policy collision" detection** — the same CSP configured across different policy models (legacy template vs Settings Catalog vs OMA-URI) is surfaced via a canonical `cspNode` + curated crosswalk, with a migrate-to-Settings-Catalog nudge. Best-effort over common settings. - **"Legacy template" flag** on the deprecated device-config template types, and **real names / value labels / doc links** for legacy Endpoint Security intents. - **Legacy template default-noise cleanup** — a Device Restrictions profile reports what it configures, not its whole serialized schema. - **Group Tag match transparency** in the picker (match / + conditions / conditional, inline scope, smart sorting, fade-in) — surfaced, not a reimplemented Entra evaluator. - **Public, zero-backend demo** on GitHub Pages (static in-browser engine over a bundled Open Intune Baseline sample tenant) + landing page + small-screen notice. **v1.2** - **Manifest → Simulator bridge** — check groups in the Assignment Manifest and simulate a device in exactly those groups, carrying any device filters you were simulating; the two views now compose instead of standing apart. - **Per-group policy "seating charts"** — each checked group's policies as a heat cluster (unique to the group vs shared vs bleed), with a named chip-list drill-down, click-a-policy-to-see-its-groups, and a dual-assignment (All Devices/Users *and* a direct group) anomaly flag. - **"Legacy policies" merged-baseline filter**, and a baseline fix dropping the `Assignments@odata.context` OData annotation that leaked in as a setting. **v1.1** - **Legacy Endpoint Security & Security Baselines** (`deviceManagement/intents`) are read and merged — BitLocker / Disk Encryption, Defender Antivirus, Firewall, ASR — and, being Windows, compared for conflicts/overlaps (see the two remaining edges under Known limitations, and the CSP-mapping item in v2). - **Docker runtime fix** — the slim server image resolved the shared package from its TypeScript source and 502'd on boot; it now resolves the built output. ## Shipped in v1.0 Delivered since the 0.5 beta: - **Autopilot enrollment layer** — Autopilot V1 deployment profiles and V2 device-preparation policies surfaced as an intermediate stage between the endpoint and its Entra groups, with device-group-based targeting, exclusion evaluation, dual-targeting detection (V1 + V2 on the same device group), and collapsible high-level deployment settings. - **Merged baseline data grid** — resizable columns, a column picker, a readable CSP-path column with a per-setting Microsoft Learn documentation link, copy-to-clipboard, and chunked rendering for large (150+ policy) tenants. - **Human-readable Settings Catalog names and values** — resolved from `settingDefinitions`, including choice option labels ("Enabled" rather than a raw option id). - **Assignment Manifest** — tenant-wide, per-OS policy → group view with an overlap summary and CSV export. - **Demo mode** — a bundled synthetic tenant so the tool can be evaluated with no app registration. - **Performance** — bounded-concurrency Graph fetching with 429/503 retry, tuned for large tenants.