Skip to content

Set recovery key: add remove state#41934

Merged
mostlikelee merged 5 commits intomainfrom
41930-remove-pending
Mar 18, 2026
Merged

Set recovery key: add remove state#41934
mostlikelee merged 5 commits intomainfrom
41930-remove-pending

Conversation

@mostlikelee
Copy link
Contributor

@mostlikelee mostlikelee commented Mar 18, 2026

Related issue: Resolves #41930

Summary by CodeRabbit

  • New Features

    • Enhanced Recovery Lock Password status model with explicit states (pending, verified, failed, removing_enforcement) and clearer status translation.
    • Added a Password Available indicator to surface whether a recovery password is present.
  • Bug Fixes

    • Improved handling of missing/raw status values so pending retries display correctly.
    • Ensures recovery lock status is computed before being shown.

@mostlikelee
Copy link
Contributor Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

Walkthrough

The PR updates Apple MDM recovery lock password handling: the MySQL datastore query now returns operation_type, a derived password_available flag, and detail. NULL delivery status from the DB is initialized to Pending. HostMDMRecoveryLockPassword gains PasswordAvailable, rawStatus, and operationType fields; SetRawStatus stores raw DB values and PopulateStatus converts raw MDM delivery statuses plus operation type into a public RecoveryLockStatus (verified, pending, failed, removing_enforcement). Service and tests now invoke PopulateStatus before exposing the status.

Possibly related PRs

  • fleetdm/fleet PR 41322: Modifies the recovery-lock password surface and the MySQL query/fields and is directly related to the status translation changes.
  • fleetdm/fleet PR 41924: Adds/propagates password_available and adjusts recovery lock password fields used by datastore and frontend, overlapping the DB and struct changes.
  • fleetdm/fleet PR 41217: Introduces recovery-lock storage and APIs that this change reads from and maps raw delivery state into the new RecoveryLockStatus.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only contains a related issue reference with no substantive explanation of changes made or testing performed despite the template requiring multiple sections. Expand the description to explain the changes made, why the pending removal state was needed, and confirm testing was performed.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Set recovery key: add remove state' clearly indicates the addition of a removal state for the recovery key, which aligns with the main objective to address the pending removal state issue.
Linked Issues check ✅ Passed Code changes successfully introduce RecoveryLockStatusRemovingEnforcement state and update status translation logic to handle CLEAR operations, addressing the pending removal state requirement from issue #41930.
Out of Scope Changes check ✅ Passed All changes are focused on implementing the recovery lock status translation layer and removing_enforcement state, directly supporting the linked issue objective with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 41930-remove-pending
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
server/fleet/hosts.go (1)

614-627: Consider adding an IsValid() method for consistency.

The DiskEncryptionStatus type (lines 677-690) includes an IsValid() method that validates the status value. For consistency and defensive coding, consider adding a similar method to RecoveryLockStatus.

♻️ Optional: Add IsValid() method
 func (s RecoveryLockStatus) addrOf() *RecoveryLockStatus {
 	return &s
 }
+
+func (s RecoveryLockStatus) IsValid() bool {
+	switch s {
+	case RecoveryLockStatusVerified, RecoveryLockStatusVerifying, RecoveryLockStatusPending, RecoveryLockStatusFailed, RecoveryLockStatusRemovingEnforcement:
+		return true
+	default:
+		return false
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/fleet/hosts.go` around lines 614 - 627, Add an IsValid() method on the
RecoveryLockStatus type (like DiskEncryptionStatus.IsValid()) that returns a
bool by checking the receiver against the allowed constants
(RecoveryLockStatusVerified, RecoveryLockStatusVerifying,
RecoveryLockStatusPending, RecoveryLockStatusFailed,
RecoveryLockStatusRemovingEnforcement); use it wherever you validate
RecoveryLockStatus values to ensure consistency and defensive checks (refer to
the RecoveryLockStatus type and its constants and the addrOf() helper when
adding the method).
server/datastore/mysql/apple_mdm_test.go (1)

11019-11036: Add the remove + NULL retry case too.

A clear retry is represented as operation_type='remove' with status=NULL, and that is the load-bearing state behind the pending-removal bug. This block only locks down the install-side NULL translation, so the clear retry path can still regress unnoticed. Please add the mirror assertion that it normalizes to RecoveryLockStatusRemovingEnforcement.

➕ Suggested test case
+	t.Run("returns removing_enforcement when remove status is NULL (retry state)", func(t *testing.T) {
+		host := test.NewHost(t, ds, "remove-null-status-host", "1.2.6.9", "removenullkey", "removenulluuid", time.Now())
+		pw := apple_mdm.GenerateRecoveryLockPassword()
+		err := ds.SetHostsRecoveryLockPasswords(ctx, []fleet.HostRecoveryLockPasswordPayload{{HostUUID: host.UUID, Password: pw}})
+		require.NoError(t, err)
+		err = ds.SetRecoveryLockVerified(ctx, host.UUID)
+		require.NoError(t, err)
+		_, err = ds.writer(ctx).ExecContext(ctx,
+			`UPDATE host_recovery_key_passwords SET operation_type = ?, status = ? WHERE host_uuid = ?`,
+			fleet.MDMOperationTypeRemove, fleet.MDMDeliveryPending, host.UUID,
+		)
+		require.NoError(t, err)
+		err = ds.ClearRecoveryLockPendingStatus(ctx, []string{host.UUID})
+		require.NoError(t, err)
+
+		status, err := ds.GetHostRecoveryLockPasswordStatus(ctx, host.UUID)
+		require.NoError(t, err)
+		require.NotNil(t, status)
+		status.PopulateStatus()
+		require.NotNil(t, status.Status)
+		assert.Equal(t, fleet.RecoveryLockStatusRemovingEnforcement, *status.Status)
+	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/apple_mdm_test.go` around lines 11019 - 11036, Add a
mirrored test case that covers the "remove + NULL" retry: create a host
(test.NewHost), set its recovery-lock operation to 'remove' (use the same
datastore APIs that set the operation_type on the host or recovery lock record),
clear the status to NULL (ds.ClearRecoveryLockPendingStatus), then call
ds.GetHostRecoveryLockPasswordStatus and status.PopulateStatus and assert that
status.Status equals fleet.RecoveryLockStatusRemovingEnforcement and
status.Detail is empty; place this alongside the existing "returns enforcing
status when status column is NULL (retry state)" test so both install and remove
NULL-retry translations are validated.
server/service/hosts_test.go (1)

811-831: Add a regression case for pending CLEAR → removing status mapping.

This update validates failed/install translation, but it doesn’t cover the core scenario from this PR’s objective: pending REMOVE should map to RecoveryLockStatusRemovingEnforcement. Please add a subtest for that translation path.

Suggested test addition
 t.Run("recovery lock password status populates for macOS", func(t *testing.T) {
@@
 	assert.Equal(t, "SetRecoveryLock command failed", hostDetail.MDM.OSSettings.RecoveryLockPassword.Detail)
 })
+
+t.Run("recovery lock password pending remove maps to removing enforcement", func(t *testing.T) {
+	pendingStatus := fleet.MDMDeliveryPending
+	ds.GetHostRecoveryLockPasswordStatusFunc = func(ctx context.Context, hostUUID string) (*fleet.HostMDMRecoveryLockPassword, error) {
+		result := &fleet.HostMDMRecoveryLockPassword{}
+		result.SetRawStatus(&pendingStatus, fleet.MDMOperationTypeRemove)
+		return result, nil
+	}
+
+	ctx := license.NewContext(t.Context(), &fleet.LicenseInfo{Tier: fleet.TierPremium})
+	hostDetail, err := svc.getHostDetails(test.UserContext(ctx, test.UserAdmin), &fleet.Host{ID: 42, Platform: "darwin", UUID: "test-uuid"}, fleet.HostDetailOptions{
+		IncludeCVEScores: false,
+		IncludePolicies:  false,
+	})
+	require.NoError(t, err)
+	require.NotNil(t, hostDetail.MDM.OSSettings.RecoveryLockPassword.Status)
+	assert.Equal(t, fleet.RecoveryLockStatusRemovingEnforcement, *hostDetail.MDM.OSSettings.RecoveryLockPassword.Status)
+})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/hosts_test.go` around lines 811 - 831, Add a subtest alongside
the existing "recovery lock password status populates for macOS" case that
exercises the pending REMOVE translation: have
ds.GetHostRecoveryLockPasswordStatusFunc return a
fleet.HostMDMRecoveryLockPassword with a pending status via
SetRawStatus(&pendingStatus, fleet.MDMOperationTypeRemove), call
svc.getHostDetails the same way, and assert that
hostDetail.MDM.OSSettings.RecoveryLockPassword.Status is non-nil and equals
fleet.RecoveryLockStatusRemovingEnforcement (and that Detail is preserved if
set); reuse the same test setup and assertions pattern
(ds.GetHostRecoveryLockPasswordStatusFuncInvoked, require.NoError,
require.NotNil) as in the existing test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@server/datastore/mysql/apple_mdm_test.go`:
- Around line 11019-11036: Add a mirrored test case that covers the "remove +
NULL" retry: create a host (test.NewHost), set its recovery-lock operation to
'remove' (use the same datastore APIs that set the operation_type on the host or
recovery lock record), clear the status to NULL
(ds.ClearRecoveryLockPendingStatus), then call
ds.GetHostRecoveryLockPasswordStatus and status.PopulateStatus and assert that
status.Status equals fleet.RecoveryLockStatusRemovingEnforcement and
status.Detail is empty; place this alongside the existing "returns enforcing
status when status column is NULL (retry state)" test so both install and remove
NULL-retry translations are validated.

In `@server/fleet/hosts.go`:
- Around line 614-627: Add an IsValid() method on the RecoveryLockStatus type
(like DiskEncryptionStatus.IsValid()) that returns a bool by checking the
receiver against the allowed constants (RecoveryLockStatusVerified,
RecoveryLockStatusVerifying, RecoveryLockStatusPending,
RecoveryLockStatusFailed, RecoveryLockStatusRemovingEnforcement); use it
wherever you validate RecoveryLockStatus values to ensure consistency and
defensive checks (refer to the RecoveryLockStatus type and its constants and the
addrOf() helper when adding the method).

In `@server/service/hosts_test.go`:
- Around line 811-831: Add a subtest alongside the existing "recovery lock
password status populates for macOS" case that exercises the pending REMOVE
translation: have ds.GetHostRecoveryLockPasswordStatusFunc return a
fleet.HostMDMRecoveryLockPassword with a pending status via
SetRawStatus(&pendingStatus, fleet.MDMOperationTypeRemove), call
svc.getHostDetails the same way, and assert that
hostDetail.MDM.OSSettings.RecoveryLockPassword.Status is non-nil and equals
fleet.RecoveryLockStatusRemovingEnforcement (and that Detail is preserved if
set); reuse the same test setup and assertions pattern
(ds.GetHostRecoveryLockPasswordStatusFuncInvoked, require.NoError,
require.NotNil) as in the existing test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e50a1b3c-d997-4e08-a8b1-bb9872b37b89

📥 Commits

Reviewing files that changed from the base of the PR and between cd54e6d and c88de84.

📒 Files selected for processing (5)
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_test.go
  • server/fleet/hosts.go
  • server/service/hosts.go
  • server/service/hosts_test.go

@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.40%. Comparing base (18f364b) to head (f181905).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
server/fleet/hosts.go 91.30% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #41934      +/-   ##
==========================================
- Coverage   66.41%   66.40%   -0.01%     
==========================================
  Files        2509     2510       +1     
  Lines      200788   201184     +396     
  Branches     8942     8942              
==========================================
+ Hits       133351   133594     +243     
- Misses      55378    55518     +140     
- Partials    12059    12072      +13     
Flag Coverage Δ
backend 68.18% <93.33%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mostlikelee mostlikelee marked this pull request as ready for review March 18, 2026 11:53
@mostlikelee mostlikelee requested a review from a team as a code owner March 18, 2026 11:53
Copilot AI review requested due to automatic review settings March 18, 2026 11:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refines macOS Recovery Lock password status reporting so the host OS Settings UI can differentiate between setting vs. clearing workflows (notably adding a “removing enforcement” state) and exposes whether a password is available.

Changes:

  • Add a dedicated RecoveryLockStatus enum and translation logic (PopulateStatus) from raw MDM delivery status + operation type.
  • Update host details service to populate/translate recovery lock status before returning API payloads.
  • Extend datastore query/tests to include operation_type and a password_available indicator, plus new test cases for pending removal.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/fleet/hosts.go Introduces RecoveryLockStatus and translation from raw MDM status/op type.
server/service/hosts.go Calls PopulateStatus() before returning recovery lock status in host details.
server/service/hosts_test.go Updates host details test to use raw status translation and assert new status values.
server/datastore/mysql/apple_mdm.go Extends recovery lock status query to include operation type and password availability; returns raw status for later translation.
server/datastore/mysql/apple_mdm_test.go Updates and expands datastore tests for translated states, including pending removal.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mostlikelee
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

✅ Actions performed

Review triggered.

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/fleet/hosts.go`:
- Around line 629-651: PopulateStatus on HostMDMRecoveryLockPassword can leave a
stale r.Status when r.rawStatus is nil; before any early return or switch
mapping, reset r.Status to nil (clear previous translated value) so reused
struct instances don't leak old state. Update
HostMDMRecoveryLockPassword.PopulateStatus to set r.Status = nil at the top (or
immediately before the early return) and then proceed with the existing
operationType/rawStatus mapping logic.
- Around line 614-622: Update the RecoveryLockPasswordStatus in
frontend/interfaces/host.ts to match the backend RecoveryLockStatus and the
already-corrected frontend/interfaces/mdm.ts: remove the incorrect "verifying"
value and add "removing_enforcement", so the union type contains "verified" |
"pending" | "removing_enforcement" | "failed"; ensure
IMdmMacOsSettings.recovery_lock_password in host.ts uses this updated
RecoveryLockPasswordStatus type so it aligns with the backend's
RecoveryLockStatus and mdm.ts definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: edc1c3c5-f1f3-4c5b-bfbb-2c0b4e87af0c

📥 Commits

Reviewing files that changed from the base of the PR and between c88de84 and f181905.

📒 Files selected for processing (2)
  • server/datastore/mysql/apple_mdm_test.go
  • server/fleet/hosts.go

Copy link
Member

@getvictor getvictor left a comment

Choose a reason for hiding this comment

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

Looks good. Please review AI agent comment.

@mostlikelee mostlikelee merged commit 35551f4 into main Mar 18, 2026
51 checks passed
@mostlikelee mostlikelee deleted the 41930-remove-pending branch March 18, 2026 14:06
mostlikelee added a commit that referenced this pull request Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recovery PW missing pending removal state

3 participants