OpenSpec example

This commit is contained in:
Victor Lyuboslavsky 2026-04-19 09:08:41 -05:00
parent 378fc322cb
commit 62ce889e83
No known key found for this signature in database
6 changed files with 619 additions and 0 deletions

33
openspec/README.md Normal file
View file

@ -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).

View file

@ -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

View file

@ -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.

View file

@ -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.)_

View file

@ -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

View file

@ -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