diff --git a/openspec/README.md b/openspec/README.md new file mode 100644 index 0000000000..8a77297f4b --- /dev/null +++ b/openspec/README.md @@ -0,0 +1,33 @@ +# OpenSpec example for Fleet story #38785 + +Strict OpenSpec format. Illustrative example of what a full spec would look like if written before +implementation of story fleetdm/fleet#38785. + +## Layout + +``` +openspec/ +└── changes/ + └── windows-setup-cancel-if-software-fails/ + ├── proposal.md # Intent, Scope, Approach + ├── design.md # Technical approach bullets + ├── tasks.md # Numbered categories with N.M task items + └── specs/ + └── mdm-windows-setup-experience/ + └── spec.md # ADDED / MODIFIED / REMOVED requirements with GIVEN/WHEN/THEN scenarios +``` + +## How strict OpenSpec answers the six behavioral contract questions + +| Question | Answered in | +|-----------------------------------|----------------------------------------------------------------------| +| What goes in? | spec.md, GIVEN clauses | +| What comes out? | spec.md, THEN clauses | +| What must stay true? | spec.md, SHALL statements plus MODIFIED Requirements | +| What side effects? | spec.md, scenarios for activity emission and DB state changes | +| How does it fail? | spec.md, timeout and failed install scenarios | +| What is explicitly out of bounds? | Not expressible. Strict OpenSpec has no non-goals section. | +| Which packages / files? | Not expressible. Strict design.md is decisions only, not file lists. | + +The last two gaps are the honest cost of staying strict. Teams that want non-goals and package lists have to +extend the format (or pair it with an ADR). diff --git a/openspec/changes/windows-setup-cancel-if-software-fails/design.md b/openspec/changes/windows-setup-cancel-if-software-fails/design.md new file mode 100644 index 0000000000..5f7650a113 --- /dev/null +++ b/openspec/changes/windows-setup-cancel-if-software-fails/design.md @@ -0,0 +1,13 @@ +# Design: Windows setup experience, cancel if software fails + +## Technical approach +- Reuse setup_experience_status_results and the canceled_setup_experience activity; no parallel Windows tables +- Store awaiting_configuration on mdm_windows_enrollments directly, not a sibling table, since the SyncML + checkin already joins that row +- Tri-state awaiting_configuration: 0 not awaiting, 1 ESP not yet issued, 2 ESP issued and in progress +- State 1 to 2 transition enqueues a single SyncML command so the outcome is stored and retried +- State 2 checkins return ESP status inline in the SyncML response, not stored, so the 3 hour window does not + explode the commands table +- require_all_software splits into require_all_software_macos and require_all_software_windows; legacy name + remains as a YAML/JSON alias via the existing renameto struct tag +- ListMDMWindowsProfilesToInstall gets a hostUUID parameter mirroring the macOS helper diff --git a/openspec/changes/windows-setup-cancel-if-software-fails/proposal.md b/openspec/changes/windows-setup-cancel-if-software-fails/proposal.md new file mode 100644 index 0000000000..a3e49acea4 --- /dev/null +++ b/openspec/changes/windows-setup-cancel-if-software-fails/proposal.md @@ -0,0 +1,27 @@ +# Proposal: Windows setup experience, cancel if software fails + +## Intent +Windows hosts enrolling via Autopilot today land on the desktop as soon as enrollment completes, with no +visible progress for profiles, setup software, or certificates, and no way for an admin to hold the user in +OOBE until "work blocking" software is present. Introduce a Windows Enrollment Status Page (ESP) that tracks +profile, SCEP, and setup-software progress during OOBE, and add a per-team `require_all_software_windows` +switch that blocks the user with a "Try again" message when any critical item fails. This brings Windows to +parity with the macOS `require_all_software` contract and gives IT admins the guarantees their organizations +require. + +## Scope +- New per-team config field `require_all_software_windows` (default false) via REST API, YAML, and UI +- Rename `require_all_software` to `require_all_software_macos` with a backward compatible alias +- Detect Autopilot OOBE enrollments and mark hosts awaiting_configuration +- Drive the Windows Enrollment Status Page via DMClient and EnrollmentStatusTracking CSPs +- Finalize the ESP: release on success, block with "Try again" when require_all_software_windows=true and + any item failed, or time out after 3 hours +- Emit canceled_setup_experience activity on Windows cancellation + +## Approach +Three phases, each independently deployable. Phase 1 adds the config field and detects Autopilot OOBE at +enrollment. Phase 2 initializes the ESP on the first qualifying Microsoft Management checkin and returns +ongoing status inline on subsequent checkins. Phase 3 finalizes the flow when items reach terminal state or +the 3 hour timeout fires. The existing setup_experience_status_results table and the +CancelPendingSetupExperienceSteps helper are reused. Windows plugs into getManagementResponse as the single +SyncML entry point. diff --git a/openspec/changes/windows-setup-cancel-if-software-fails/specs/mdm-windows-setup-experience/spec.md b/openspec/changes/windows-setup-cancel-if-software-fails/specs/mdm-windows-setup-experience/spec.md new file mode 100644 index 0000000000..de73fb21b9 --- /dev/null +++ b/openspec/changes/windows-setup-cancel-if-software-fails/specs/mdm-windows-setup-experience/spec.md @@ -0,0 +1,225 @@ +# Delta for mdm-windows-setup-experience + +## ADDED Requirements + +### Requirement: Windows require_all_software_windows config + +Fleet SHALL expose a per-team boolean config field `require_all_software_windows`, defaulting to `false`, +that controls whether the Windows setup experience blocks the end user when any setup-experience software +fails to install. + +#### Scenario: Default value on a new team + +- GIVEN a newly created team with Windows MDM enabled +- WHEN an admin reads the team's setup experience config +- THEN `require_all_software_windows` is `false` + +#### Scenario: Update via REST API + +- GIVEN a team exists with Windows MDM enabled +- WHEN an admin sends `PATCH /api/_version_/fleet/setup_experience` with + `{"fleet_id": 1, "require_all_software_windows": true}` +- THEN the field is persisted +- AND subsequent reads return `true` + +#### Scenario: Update via GitOps + +- GIVEN a team YAML file sets `setup_experience.require_all_software_windows: true` +- WHEN `fleetctl apply` processes the file +- THEN the stored config reflects `true` +- AND exporting the team back to YAML round-trips the value + +#### Scenario: Rejected when Windows MDM is disabled + +- GIVEN a team where Windows MDM is NOT enabled +- WHEN an admin attempts to set `require_all_software_windows=true` +- THEN the request is rejected with a validation error + +### Requirement: Windows hosts awaiting configuration state + +Fleet SHALL track whether each Windows-enrolled host is awaiting setup-experience completion using a +tri-state column `awaiting_configuration` on `mdm_windows_enrollments` (0 = not awaiting, 1 = ESP not yet +issued, 2 = ESP issued and in progress). + +#### Scenario: Autopilot OOBE enrollment sets awaiting + +- GIVEN a Windows host enrolls via Autopilot +- AND the request includes an Azure JWT or WSTEP binary security token +- AND the request indicates `NotInOobe=false` +- WHEN Fleet stores the enrollment +- THEN `awaiting_configuration=1` +- AND `awaiting_configuration_at=NOW()` + +#### Scenario: Programmatic fleetd enrollment does not set awaiting + +- GIVEN a Windows host enrolls via fleetd using an orbit node key +- WHEN Fleet stores the enrollment +- THEN `awaiting_configuration=0` + +#### Scenario: Post-OOBE Autopilot re-enrollment clears awaiting + +- GIVEN a Windows host that previously completed setup re-enrolls via Autopilot +- AND the request indicates `NotInOobe=true` +- WHEN Fleet upserts the enrollment row +- THEN `awaiting_configuration=0` + +### Requirement: Initial ESP command on first qualifying checkin + +Fleet SHALL send an initial Windows ESP SyncML command covering profiles, SCEP certificates, and setup +software on the first Microsoft Management checkin for a host with `awaiting_configuration=1` and a known +`host_uuid`, and SHALL then transition the host to `awaiting_configuration=2`. + +#### Scenario: Software defined, host recently orbit-enrolled + +- GIVEN a host has `awaiting_configuration=1` and orbit-enrolled 30 minutes ago +- AND its team has setup-experience software items defined +- WHEN the host sends a Microsoft Management checkin +- THEN Fleet enqueues a SyncML command with DMClient ExpectedPolicies for each profile's top-most node +- AND EnrollmentStatusTracking entries for each setup software item +- AND SCEP entries for profiles containing SCEP certificates +- AND DMClient `TimeoutUntilSyncFailure=3h` +- AND the host row is updated to `awaiting_configuration=2` + +#### Scenario: No software defined and orbit registered less than 10 minutes ago + +- GIVEN a host has `awaiting_configuration=1` and orbit-enrolled 3 minutes ago +- AND its team has no setup-experience software items defined +- WHEN the host sends a Microsoft Management checkin +- THEN Fleet does nothing and leaves `awaiting_configuration=1` +- AND no SyncML command is enqueued + +#### Scenario: No software defined and orbit registered more than 10 minutes ago + +- GIVEN a host has `awaiting_configuration=1` and orbit-enrolled 15 minutes ago +- AND its team has no setup-experience software items defined +- WHEN the host sends a Microsoft Management checkin +- THEN Fleet proceeds to the release path +- AND `awaiting_configuration` is set to 0 + +### Requirement: Ongoing ESP status sync is inline, not stored + +Fleet SHALL return current Windows ESP tracking status inline in the SyncML response body on every Microsoft +Management checkin while `awaiting_configuration=2`, and SHALL NOT enqueue these updates as stored commands. + +#### Scenario: Three consecutive checkins in state 2 + +- GIVEN a host has `awaiting_configuration=2` +- WHEN the host makes three consecutive Microsoft Management checkins +- THEN each response contains the current EnrollmentStatusTracking entries reflecting + `setup_experience_status_results` +- AND no new rows are written to the stored MDM commands table +- AND dropping any one response leaves the next response self-sufficient + +### Requirement: Terminal-state release and cancellation + +Fleet SHALL finalize the Windows setup experience when the outcome is decided: release the user when the +flow succeeded or when `require_all_software_windows=false` and all items are terminal; block the user with +a "Try again" message when `require_all_software_windows=true` and any item failed or the flow timed out. + +#### Scenario: All items succeed + +- GIVEN a host has `awaiting_configuration=2` +- AND all `setup_experience_status_results` rows are `success` +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet enqueues a final ESP command releasing the user to the desktop +- AND `awaiting_configuration=0` + +#### Scenario: require_all_software_windows=true with a failed install + +- GIVEN a host has `awaiting_configuration=2` and its team has `require_all_software_windows=true` +- AND at least one setup-experience software item is in `failure` state +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet cancels remaining pending items via `CancelPendingSetupExperienceSteps` +- AND enqueues a final ESP command with `BlockInStatusPage=true` +- AND `CustomErrorText="Critical software failed to install. Please try again. If this keeps happening, + please contact your IT admin."` +- AND emits a `canceled_setup_experience` activity citing the first failed software item by `display_name` + (falling back to `name`) +- AND `awaiting_configuration=0` + +#### Scenario: require_all_software_windows=false with a failed install + +- GIVEN a host has `awaiting_configuration=2` and its team has `require_all_software_windows=false` +- AND at least one setup-experience software item is in `failure` state +- AND all other items are terminal +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet enqueues a final ESP command releasing the user to the desktop +- AND `CustomErrorText` surfaces the error to the user but does not block +- AND `awaiting_configuration=0` + +### Requirement: 3 hour timeout + +Fleet SHALL treat a host as timed out if `now - awaiting_configuration_at > 3 hours`, regardless of pending +items. + +#### Scenario: Timeout with require_all_software_windows=true + +- GIVEN a host has `awaiting_configuration=2` and `awaiting_configuration_at` 3 hours 5 minutes ago +- AND its team has `require_all_software_windows=true` +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet cancels remaining pending items +- AND enqueues a blocking ESP command with `CustomErrorText` +- AND emits a `canceled_setup_experience` activity +- AND `awaiting_configuration=0` + +#### Scenario: Timeout with require_all_software_windows=false + +- GIVEN a host has `awaiting_configuration=2` and `awaiting_configuration_at` 3 hours 5 minutes ago +- AND its team has `require_all_software_windows=false` +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet marks remaining items failed and enqueues a releasing ESP command +- AND `awaiting_configuration=0` + +### Requirement: Windows checkbox on Setup experience > Install software + +Fleet SHALL render a "Cancel setup if software fails" checkbox on the Windows tab of the Setup experience +install-software page that reads and writes `require_all_software_windows` through the same +`/api/_version_/fleet/setup_experience` endpoint used by macOS. + +#### Scenario: Toggle the checkbox + +- GIVEN an admin is on Setup experience > Install software, Windows tab +- AND `require_all_software_windows=false` +- WHEN the admin checks the box and clicks Save +- THEN the UI calls `updateRequireAllSoftwareWindows(true)` +- AND the admin sees a success state +- AND reloading the page shows the box checked + +### Requirement: canceled_setup_experience activity extended to Windows + +Fleet SHALL emit the existing `canceled_setup_experience` activity type for Windows cancellations with the +same schema used for macOS, naming the first failed software item using `display_name` with a fallback to +`name`. + +#### Scenario: Windows cancellation produces an activity + +- GIVEN a Windows host setup experience is canceled due to a failed install of "Microsoft Office" +- WHEN Fleet finalizes the cancellation +- THEN an activity of type `canceled_setup_experience` is recorded +- AND the payload references "Microsoft Office" via `display_name` +- AND the activity is visible in both the global activity feed and the host's activity feed + +## MODIFIED Requirements + +### Requirement: require_all_software field rename + +Fleet SHALL treat the existing `require_all_software` config field as an alias for +`require_all_software_macos`. Previously, `require_all_software` was the authoritative macOS-only field. + +#### Scenario: Legacy YAML still applies + +- GIVEN a GitOps YAML file sets `macos_setup.require_all_software: true` (legacy form) +- WHEN `fleetctl apply` processes the file +- THEN the stored value for `require_all_software_macos` is `true` +- AND no deprecation error is raised in this release + +#### Scenario: Exported YAML uses the new name + +- GIVEN a team has `require_all_software_macos=true` +- WHEN the team is exported to YAML via GitOps +- THEN the exported file contains `setup_experience.require_all_software_macos: true` +- AND not the legacy `macos_setup.require_all_software` form + +## REMOVED Requirements + +_(None. The legacy `require_all_software` field remains readable; it is renamed, not removed.)_ diff --git a/openspec/changes/windows-setup-cancel-if-software-fails/tasks.md b/openspec/changes/windows-setup-cancel-if-software-fails/tasks.md new file mode 100644 index 0000000000..0710cffa63 --- /dev/null +++ b/openspec/changes/windows-setup-cancel-if-software-fails/tasks.md @@ -0,0 +1,103 @@ +# Tasks + +## 1. Database migration +- [ ] 1.1 Add awaiting_configuration TINYINT NOT NULL DEFAULT 0 and awaiting_configuration_at DATETIME(6) + NULL to mdm_windows_enrollments +- [ ] 1.2 Update server/datastore/mysql/schema.sql +- [ ] 1.3 Migration test covering default values on existing rows + +## 2. Config struct and rename +- [ ] 2.1 Rename MacOSSetup.RequireAllSoftware to RequireAllSoftwareMacOS +- [ ] 2.2 Add RequireAllSoftwareWindows *bool to the setup experience struct +- [ ] 2.3 Add renameto tag so require_all_software deserializes to the macOS field +- [ ] 2.4 Support both macos_setup and setup_experience YAML parent keys for one release + +## 3. REST API and GitOps +- [ ] 3.1 Extend MDMAppleSetupPayload with RequireAllSoftwareWindows *bool +- [ ] 3.2 Wire the EE handler in ee/server/service/mdm.go to persist it +- [ ] 3.3 Team config apply path in ee/server/service/teams.go handles the new field +- [ ] 3.4 Validate that Windows MDM is enabled before accepting the field +- [ ] 3.5 Update cmd/fleetctl/fleetctl/testdata/ golden files +- [ ] 3.6 Document the field in docs/REST API/rest-api.md + +## 4. Detection at enrollment +- [ ] 4.1 Classify the enrollment source in storeWindowsMDMEnrolledDevice using the binary security token type +- [ ] 4.2 Set awaiting_configuration=1 and awaiting_configuration_at=NOW() when source is Autopilot (JWT or + WSTEP) and NotInOobe=false +- [ ] 4.3 Set awaiting_configuration=0 otherwise +- [ ] 4.4 Verify ON DUPLICATE KEY UPDATE correctly updates the columns on re-enrollment +- [ ] 4.5 Integration test: fleetd programmatic enroll leaves awaiting_configuration=0 +- [ ] 4.6 Integration test: simulated Autopilot JWT enroll with NotInOobe=false sets awaiting_configuration=1 +- [ ] 4.7 Integration test: Autopilot re-enroll post-OOBE resets to 0 + +## 5. Frontend config plumbing +- [ ] 5.1 Add field to frontend/interfaces/config.ts and frontend/interfaces/team.ts +- [ ] 5.2 Add field to frontend/__mocks__/configMock.ts +- [ ] 5.3 Add mdmAPI.updateRequireAllSoftwareWindows in frontend/services/entities/mdm.ts + +## 6. DMClient and EnrollmentStatusTracking SyncML helpers +- [ ] 6.1 Add DMClient CSP path constants for ExpectedPolicies, TimeoutUntilSyncFailure, CustomErrorText, and + BlockInStatusPage in server/mdm/microsoft/syncml/syncml.go +- [ ] 6.2 Add EnrollmentStatusTracking CSP helpers for software tracking entries +- [ ] 6.3 Add SCEP certificate ESP node helpers +- [ ] 6.4 Unit tests for each helper with golden SyncML XML assertions + +## 7. Single-host profile listing +- [ ] 7.1 Add hostUUID string parameter to ListMDMWindowsProfilesToInstall mirroring the macOS helper +- [ ] 7.2 Update existing callers to pass empty string +- [ ] 7.3 Test for single-host filtering + +## 8. Initial ESP command generation +- [ ] 8.1 On getManagementResponse with awaiting_configuration=1 and host_uuid set, query setup software for + the host's team +- [ ] 8.2 If no items and orbit-enrolled less than 10 minutes ago, do nothing +- [ ] 8.3 If no items and at least 10 minutes, proceed to release path +- [ ] 8.4 If items exist, fetch applicable profiles and current setup_experience_status_results +- [ ] 8.5 Build initial SyncML command: profile top-most nodes, SCEP nodes, software tracking entries +- [ ] 8.6 Set TimeoutUntilSyncFailure to 3 hours +- [ ] 8.7 Enqueue as stored command; transition row to awaiting_configuration=2 +- [ ] 8.8 Integration test: first checkin after orbit registers produces the expected SyncML + +## 9. Ongoing status sync +- [ ] 9.1 On getManagementResponse with awaiting_configuration=2, fetch current setup_experience_status_results +- [ ] 9.2 Build EnrollmentStatusTracking entries reflecting current state of each item +- [ ] 9.3 Return inline in SyncML response; do not enqueue +- [ ] 9.4 Integration test: three consecutive checkins return consistent status with no duplicate stored commands + +## 10. Frontend checkbox +- [ ] 10.1 Add checkbox block for platform windows in InstallSoftwareForm.tsx at the existing macOS block +- [ ] 10.2 Add requireAllSoftwareWindows state and handler +- [ ] 10.3 Pass savedRequireAllSoftwareWindows prop from parent +- [ ] 10.4 Extend shouldUpdateRequireAll to trigger for Windows +- [ ] 10.5 Wire onClickSave to call updateRequireAllSoftwareWindows +- [ ] 10.6 Update macOS checkbox label and tooltip to match the new Windows copy +- [ ] 10.7 Verify canceled_setup_experience appears in the activity filter dropdown and feed +- [ ] 10.8 Test coverage for the new checkbox + +## 11. Timeout path +- [ ] 11.1 Compute age = now - awaiting_configuration_at in getManagementResponse state 2 +- [ ] 11.2 If age > 3h, call CancelPendingSetupExperienceSteps +- [ ] 11.3 If require_all_software_windows=true, enqueue final ESP with BlockInStatusPage=true and + CustomErrorText +- [ ] 11.4 If require_all_software_windows=false, enqueue final ESP marking remaining items failed but + allowing the user through +- [ ] 11.5 Emit canceled_setup_experience activity using display_name with fallback to name for the first + failed software item +- [ ] 11.6 Set awaiting_configuration=0 + +## 12. Blocking failure path +- [ ] 12.1 If require_all_software_windows=true and any profile or software is in failure state, cancel + remaining, emit activity, enqueue blocking ESP, set awaiting_configuration=0 + +## 13. Release path +- [ ] 13.1 If all items terminal, enqueue final ESP; include CustomErrorText if any errors exist; set + awaiting_configuration=0 + +## 14. macOS activity text update +- [ ] 14.1 Add "End user was asked to restart" to macOS canceled_setup_experience activity text + +## 15. Docs and QA +- [ ] 15.1 Update docs/Using Fleet/Windows & Linux setup experience.md with SHIFT+F10 breakglass and remote + fleet script instructions +- [ ] 15.2 Load test: verify no additional steady-state load from the new columns and checkin paths +- [ ] 15.3 Execute all test plan items from issue #38785 diff --git a/openspec/specs/mdm-windows-setup-experience/spec.md b/openspec/specs/mdm-windows-setup-experience/spec.md new file mode 100644 index 0000000000..72c7408200 --- /dev/null +++ b/openspec/specs/mdm-windows-setup-experience/spec.md @@ -0,0 +1,218 @@ +# mdm-windows-setup-experience + +## Requirements + +### Requirement: Windows require_all_software_windows config + +Fleet SHALL expose a per-team boolean config field `require_all_software_windows`, defaulting to `false`, +that controls whether the Windows setup experience blocks the end user when any setup-experience software +fails to install. + +#### Scenario: Default value on a new team + +- GIVEN a newly created team with Windows MDM enabled +- WHEN an admin reads the team's setup experience config +- THEN `require_all_software_windows` is `false` + +#### Scenario: Update via REST API + +- GIVEN a team exists with Windows MDM enabled +- WHEN an admin sends `PATCH /api/_version_/fleet/setup_experience` with + `{"fleet_id": 1, "require_all_software_windows": true}` +- THEN the field is persisted +- AND subsequent reads return `true` + +#### Scenario: Update via GitOps + +- GIVEN a team YAML file sets `setup_experience.require_all_software_windows: true` +- WHEN `fleetctl apply` processes the file +- THEN the stored config reflects `true` +- AND exporting the team back to YAML round-trips the value + +#### Scenario: Rejected when Windows MDM is disabled + +- GIVEN a team where Windows MDM is NOT enabled +- WHEN an admin attempts to set `require_all_software_windows=true` +- THEN the request is rejected with a validation error + +### Requirement: Windows hosts awaiting configuration state + +Fleet SHALL track whether each Windows-enrolled host is awaiting setup-experience completion using a +tri-state column `awaiting_configuration` on `mdm_windows_enrollments` (0 = not awaiting, 1 = ESP not yet +issued, 2 = ESP issued and in progress). + +#### Scenario: Autopilot OOBE enrollment sets awaiting + +- GIVEN a Windows host enrolls via Autopilot +- AND the request includes an Azure JWT or WSTEP binary security token +- AND the request indicates `NotInOobe=false` +- WHEN Fleet stores the enrollment +- THEN `awaiting_configuration=1` +- AND `awaiting_configuration_at=NOW()` + +#### Scenario: Programmatic fleetd enrollment does not set awaiting + +- GIVEN a Windows host enrolls via fleetd using an orbit node key +- WHEN Fleet stores the enrollment +- THEN `awaiting_configuration=0` + +#### Scenario: Post-OOBE Autopilot re-enrollment clears awaiting + +- GIVEN a Windows host that previously completed setup re-enrolls via Autopilot +- AND the request indicates `NotInOobe=true` +- WHEN Fleet upserts the enrollment row +- THEN `awaiting_configuration=0` + +### Requirement: Initial ESP command on first qualifying checkin + +Fleet SHALL send an initial Windows ESP SyncML command covering profiles, SCEP certificates, and setup +software on the first Microsoft Management checkin for a host with `awaiting_configuration=1` and a known +`host_uuid`, and SHALL then transition the host to `awaiting_configuration=2`. + +#### Scenario: Software defined, host recently orbit-enrolled + +- GIVEN a host has `awaiting_configuration=1` and orbit-enrolled 30 minutes ago +- AND its team has setup-experience software items defined +- WHEN the host sends a Microsoft Management checkin +- THEN Fleet enqueues a SyncML command with DMClient ExpectedPolicies for each profile's top-most node +- AND EnrollmentStatusTracking entries for each setup software item +- AND SCEP entries for profiles containing SCEP certificates +- AND DMClient `TimeoutUntilSyncFailure=3h` +- AND the host row is updated to `awaiting_configuration=2` + +#### Scenario: No software defined and orbit registered less than 10 minutes ago + +- GIVEN a host has `awaiting_configuration=1` and orbit-enrolled 3 minutes ago +- AND its team has no setup-experience software items defined +- WHEN the host sends a Microsoft Management checkin +- THEN Fleet does nothing and leaves `awaiting_configuration=1` +- AND no SyncML command is enqueued + +#### Scenario: No software defined and orbit registered more than 10 minutes ago + +- GIVEN a host has `awaiting_configuration=1` and orbit-enrolled 15 minutes ago +- AND its team has no setup-experience software items defined +- WHEN the host sends a Microsoft Management checkin +- THEN Fleet proceeds to the release path +- AND `awaiting_configuration` is set to 0 + +### Requirement: Ongoing ESP status sync is inline, not stored + +Fleet SHALL return current Windows ESP tracking status inline in the SyncML response body on every Microsoft +Management checkin while `awaiting_configuration=2`, and SHALL NOT enqueue these updates as stored commands. + +#### Scenario: Three consecutive checkins in state 2 + +- GIVEN a host has `awaiting_configuration=2` +- WHEN the host makes three consecutive Microsoft Management checkins +- THEN each response contains the current EnrollmentStatusTracking entries reflecting + `setup_experience_status_results` +- AND no new rows are written to the stored MDM commands table +- AND dropping any one response leaves the next response self-sufficient + +### Requirement: Terminal-state release and cancellation + +Fleet SHALL finalize the Windows setup experience when the outcome is decided: release the user when the +flow succeeded or when `require_all_software_windows=false` and all items are terminal; block the user with +a "Try again" message when `require_all_software_windows=true` and any item failed or the flow timed out. + +#### Scenario: All items succeed + +- GIVEN a host has `awaiting_configuration=2` +- AND all `setup_experience_status_results` rows are `success` +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet enqueues a final ESP command releasing the user to the desktop +- AND `awaiting_configuration=0` + +#### Scenario: require_all_software_windows=true with a failed install + +- GIVEN a host has `awaiting_configuration=2` and its team has `require_all_software_windows=true` +- AND at least one setup-experience software item is in `failure` state +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet cancels remaining pending items via `CancelPendingSetupExperienceSteps` +- AND enqueues a final ESP command with `BlockInStatusPage=true` +- AND `CustomErrorText="Critical software failed to install. Please try again. If this keeps happening, + please contact your IT admin."` +- AND emits a `canceled_setup_experience` activity citing the first failed software item by `display_name` + (falling back to `name`) +- AND `awaiting_configuration=0` + +#### Scenario: require_all_software_windows=false with a failed install + +- GIVEN a host has `awaiting_configuration=2` and its team has `require_all_software_windows=false` +- AND at least one setup-experience software item is in `failure` state +- AND all other items are terminal +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet enqueues a final ESP command releasing the user to the desktop +- AND `CustomErrorText` surfaces the error to the user but does not block +- AND `awaiting_configuration=0` + +### Requirement: 3 hour timeout + +Fleet SHALL treat a host as timed out if `now - awaiting_configuration_at > 3 hours`, regardless of pending +items. + +#### Scenario: Timeout with require_all_software_windows=true + +- GIVEN a host has `awaiting_configuration=2` and `awaiting_configuration_at` 3 hours 5 minutes ago +- AND its team has `require_all_software_windows=true` +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet cancels remaining pending items +- AND enqueues a blocking ESP command with `CustomErrorText` +- AND emits a `canceled_setup_experience` activity +- AND `awaiting_configuration=0` + +#### Scenario: Timeout with require_all_software_windows=false + +- GIVEN a host has `awaiting_configuration=2` and `awaiting_configuration_at` 3 hours 5 minutes ago +- AND its team has `require_all_software_windows=false` +- WHEN the host makes a Microsoft Management checkin +- THEN Fleet marks remaining items failed and enqueues a releasing ESP command +- AND `awaiting_configuration=0` + +### Requirement: Windows checkbox on Setup experience > Install software + +Fleet SHALL render a "Cancel setup if software fails" checkbox on the Windows tab of the Setup experience +install-software page that reads and writes `require_all_software_windows` through the same +`/api/_version_/fleet/setup_experience` endpoint used by macOS. + +#### Scenario: Toggle the checkbox + +- GIVEN an admin is on Setup experience > Install software, Windows tab +- AND `require_all_software_windows=false` +- WHEN the admin checks the box and clicks Save +- THEN the UI calls `updateRequireAllSoftwareWindows(true)` +- AND the admin sees a success state +- AND reloading the page shows the box checked + +### Requirement: canceled_setup_experience activity covers Windows + +Fleet SHALL emit the `canceled_setup_experience` activity type for Windows cancellations with the same +schema used for macOS, naming the first failed software item using `display_name` with a fallback to `name`. + +#### Scenario: Windows cancellation produces an activity + +- GIVEN a Windows host setup experience is canceled due to a failed install of "Microsoft Office" +- WHEN Fleet finalizes the cancellation +- THEN an activity of type `canceled_setup_experience` is recorded +- AND the payload references "Microsoft Office" via `display_name` +- AND the activity is visible in both the global activity feed and the host's activity feed + +### Requirement: require_all_software field naming and backward compatibility + +Fleet SHALL expose the macOS variant of the setting as `require_all_software_macos` and SHALL continue to +accept the legacy name `require_all_software` as a YAML/JSON alias for `require_all_software_macos`. Exported +configurations SHALL use the new name under `setup_experience`. + +#### Scenario: Legacy YAML still applies + +- GIVEN a GitOps YAML file sets `macos_setup.require_all_software: true` (legacy form) +- WHEN `fleetctl apply` processes the file +- THEN the stored value for `require_all_software_macos` is `true` + +#### Scenario: Exported YAML uses the new name + +- GIVEN a team has `require_all_software_macos=true` +- WHEN the team is exported to YAML via GitOps +- THEN the exported file contains `setup_experience.require_all_software_macos: true` +- AND not the legacy `macos_setup.require_all_software` form