Skip to content

iOS/iPadOS device vitals: storage + DeviceInformation command (1/2) - #50046

Merged
nulmete merged 4 commits into
49984-ios-ipados-vitalsfrom
49984-vitals-storage-and-command
Aug 4, 2026
Merged

iOS/iPadOS device vitals: storage + DeviceInformation command (1/2)#50046
nulmete merged 4 commits into
49984-ios-ipados-vitalsfrom
49984-vitals-storage-and-command

Conversation

@nulmete

@nulmete nulmete commented Jul 28, 2026

Copy link
Copy Markdown
Member

Related issue: Relates to #49984

This is PR 1 of 2 for #49984, split for ease of review and stacked onto a feature branch (49984-ios-ipados-vitals). This PR adds the MySQL storage (host_mdm_apple_device_vitals, host_mdm_apple_service_subscriptions) for the 29 additional iOS/iPadOS vitals, and expands the DeviceInformation MDM command's Queries array to request the corresponding Apple keys. It does not yet parse or persist any new ack data — that's PR 2 (#50047), stacked on top of this branch.

Checklist for submitter

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

It will be included in the feature branch.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually (see results on PR2)

Summary by CodeRabbit

  • New Features

    • Added support for collecting and storing Apple MDM device vitals and cellular service subscription details.
    • Expanded Apple device information requests to include the full set of supported query fields.
    • Added database support for retaining device vitals and subscription data.
  • Bug Fixes

    • Host deletion now also removes associated Apple MDM vitals and subscription records.
  • Tests

    • Added coverage for vitals updates, nullable values, subscription synchronization, database constraints, and device information requests.

…e vitals

Adds host_mdm_apple_device_vitals and host_mdm_apple_service_subscriptions
tables plus the datastore method to persist them, and expands the
DeviceInformation MDM command to request the additional Apple query keys
needed to populate them. Part of #49984.
@nulmete

nulmete commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds Apple MDM device vitals and service subscription models, datastore APIs, MySQL tables, migrations, and persistence with update/insert semantics. Service subscriptions are refreshed by removing stale slots and upserting current slots. Device Information commands now generate their query list from an exported key slice. Host deletion cleanup includes the new tables, with datastore, migration, command, null-handling, replacement, and cleanup tests.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: Apple device vitals storage plus the DeviceInformation command update.
Description check ✅ Passed The description includes the related issue, a clear summary, and key testing notes, though some template checklist sections are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 49984-vitals-storage-and-command

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
server/mdm/apple/commander.go (1)

463-501: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Avoid exposing a mutable global query slice.

DeviceInformationQueryKeys can be reassigned, reordered, appended to, or have individual entries mutated by callers, changing every subsequent command. Keep the canonical list private and expose a copy/accessor if external consumers need it. Confirm whether this export is intended as public API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/mdm/apple/commander.go` around lines 463 - 501, Make the canonical
DeviceInformation query list private so callers cannot reassign or mutate shared
state, and update internal references to use it. If external consumers require
access, expose an accessor that returns a fresh copy in the declared request
order; otherwise remove the export and confirm no public API depends on it.
server/mdm/apple/commander_test.go (1)

1136-1136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not discard push-factory setup errors.

Capture the error from newMockAPNSPushProviderFactory and fail the test before constructing the push service, so setup failures are reported at their source.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/mdm/apple/commander_test.go` at line 1136, Update the test setup
around newMockAPNSPushProviderFactory to capture and assert its returned error.
Fail the test immediately when factory creation fails, before constructing the
push service, while preserving the existing pushFactory usage for successful
setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/datastore/mysql/apple_mdm_device_vitals.go`:
- Around line 183-186: Replace the update-then-insert fallbacks in the
device-vitals write flow at
server/datastore/mysql/apple_mdm_device_vitals.go:183-186 and subscription-slot
write flow at server/datastore/mysql/apple_mdm_device_vitals.go:266-269 with
atomic MySQL upserts that handle unchanged updates without attempting a
duplicate insert. Add coverage verifying that resubmitting an identical payload
succeeds.

In `@server/fleet/mdm_apple_device_vitals.go`:
- Around line 114-120: Update the persistence mapping for
DevicePropertiesAttestation in the Apple MDM device vitals datastore model so
the [][]byte certificate chain is serialized to and restored from its JSON
column alongside AccessibilitySettings, OrganizationInfo, and MDMOptions. Add
the corresponding database row/schema mapping and a round-trip test covering
non-empty attestation data; otherwise remove the field’s persisted JSON
contract.

In `@server/mdm/apple/commander_test.go`:
- Around line 1177-1178: Update the assertions in the DeviceInformation command
test to compare gotCommand.Command.Queries against an explicit, fixed list
containing all 29 required query names in the expected order, rather than
reusing DeviceInformationQueryKeys. Keep the existing RequestType assertion
unchanged.

---

Nitpick comments:
In `@server/mdm/apple/commander_test.go`:
- Line 1136: Update the test setup around newMockAPNSPushProviderFactory to
capture and assert its returned error. Fail the test immediately when factory
creation fails, before constructing the push service, while preserving the
existing pushFactory usage for successful setup.

In `@server/mdm/apple/commander.go`:
- Around line 463-501: Make the canonical DeviceInformation query list private
so callers cannot reassign or mutate shared state, and update internal
references to use it. If external consumers require access, expose an accessor
that returns a fresh copy in the declared request order; otherwise remove the
export and confirm no public API depends on it.
🪄 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 Plus

Run ID: 24e0a927-82fc-4d8f-8146-01d061fdefd4

📥 Commits

Reviewing files that changed from the base of the PR and between 0504e59 and 51fb53d.

📒 Files selected for processing (12)
  • server/datastore/mysql/apple_mdm_device_vitals.go
  • server/datastore/mysql/apple_mdm_device_vitals_test.go
  • server/datastore/mysql/hosts.go
  • server/datastore/mysql/hosts_test.go
  • server/datastore/mysql/migrations/tables/20260727191311_CreateHostMDMAppleDeviceVitals.go
  • server/datastore/mysql/migrations/tables/20260727191311_CreateHostMDMAppleDeviceVitals_test.go
  • server/datastore/mysql/schema.sql
  • server/fleet/datastore.go
  • server/fleet/mdm_apple_device_vitals.go
  • server/mdm/apple/commander.go
  • server/mdm/apple/commander_test.go
  • server/mock/datastore_mock.go

Comment on lines +183 to +186
if affected, _ := result.RowsAffected(); affected == 0 {
if _, err := sqlx.NamedExecContext(ctx, tx, deviceVitalsInsertStmt, row); err != nil {
return ctxerr.Wrap(ctx, err, "insert host mdm apple device vitals")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect whether the MySQL connection enables matched-row reporting and find
# other affected-row-based insert fallbacks.
rg -n --glob '*.go' 'clientFoundRows|mysql\.Config|RowsAffected\(' server
rg -n 'go-sql-driver/mysql' go.mod go.sum 2>/dev/null || true

Repository: fleetdm/fleet

Length of output: 14334


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Common MySQL config =="
sed -n '130,170p' server/platform/mysql/common.go

echo
echo "== Device vitals lines 140-205 =="
sed -n '140,205p' server/datastore/mysql/apple_mdm_device_vitals.go

echo
echo "== Subscription lines 240-275 =="
sed-140,275p server/datastore/mysql/apple_mdm_device_vitals.go

echo
echo "== Relevant migration/table definitions =="
rg -n "mdm_apple_device_vitals|host_service_subscription|apples_service_subscription|apples_service_subscription_slots|apples_service_subscription_slots" server/datastore/mysql server/datastore -g '*.go' | head -120

Repository: fleetdm/fleet

Length of output: 5434


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Subscription upsert lines 240-275 =="
sed -n '240,275p' server/datastore/mysql/apple_mdm_device_vitals.go

echo
echo "== Vitals/update table/model references =="
rg -n "host_mdm_apple_device_vitals|host_mdm_apple_service_subscriptions|SetOrUpdateHostMDMAppleDeviceVitals" server/datastore mysql server -g '*.go' | head -200

echo
echo "== Nearby service subscription implementations =="
rg -n "host_mdm_apple_service_subscriptions" server/datastore/mysql -g '*.go' -A 10 -B 10 | head -220

Repository: fleetdm/fleet

Length of output: 15765


Make both writes atomic upserts. Fleet configures the MySQL client with clientFoundRows=true, so unchanged UPDATEs report zero affected rows and fall through to INSERT, causing duplicate-key failures on repeated identical acks. Use atomic device-vitals and subscription slot upserts instead of update-then-insert fallbacks, and add coverage for resubmitting the same payload.

  • server/datastore/mysql/apple_mdm_device_vitals.go#L183-L186
  • server/datastore/mysql/apple мdm_device_vitals.go#L266-L269
📍 Affects 1 file
  • server/datastore/mysql/apple_mdm_device_vitals.go#L183-L186 (this comment)
  • server/datastore/mysql/apple_mdm_device_vitals.go#L266-L269
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/datastore/mysql/apple_mdm_device_vitals.go` around lines 183 - 186,
Replace the update-then-insert fallbacks in the device-vitals write flow at
server/datastore/mysql/apple_mdm_device_vitals.go:183-186 and subscription-slot
write flow at server/datastore/mysql/apple_mdm_device_vitals.go:266-269 with
atomic MySQL upserts that handle unchanged updates without attempting a
duplicate insert. Add coverage verifying that resubmitting an identical payload
succeeds.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Verified against current code — this doesn't hold. Fleet sets clientFoundRows=true in the DSN (server/platform/mysql/common.go:154), and per MySQL's CLIENT_FOUND_ROWS semantics that makes RowsAffected() report rows matched, not rows changed — the opposite direction from what's described. So resubmitting an identical payload still reports affected=1 and never falls through to the INSERT. Added TestHostMDMAppleDeviceVitals/ResubmitIdenticalPayload (covers both the main row and a subscription slot) in 5aaa3c9 — passes cleanly, confirming there's no duplicate-key failure. Keeping update-then-insert as-is (also matches the pattern used by every other SetOrUpdate* method in this codebase); the residual near-simultaneous-write race is already handled by logging rather than failing the check-in.

Comment on lines +114 to +120
// DevicePropertiesAttestation is the raw DER certificate chain Apple
// returns (rooted at the Apple Enterprise Attestation Root CA; per manual
// testing against a physical iPhone, typically 2 certificates — the leaf
// and the "Apple Enterprise Attestation Sub CA" intermediate). Fleet does
// not currently parse the chain's custom OIDs into a derived signal. See
// https://github.com/fleetdm/fleet/issues/49984.
DevicePropertiesAttestation [][]byte `db:"-"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist DevicePropertiesAttestation consistently with the model contract.

This field is documented as a persisted JSON column, but server/datastore/mysql/apple_mdm_device_vitals.go:65-112 only marshals AccessibilitySettings, OrganizationInfo, and MDMOptions. The attestation chain is therefore dropped on every write. Add the row/schema mapping and a round-trip test, or remove the persistence claim until implemented.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/fleet/mdm_apple_device_vitals.go` around lines 114 - 120, Update the
persistence mapping for DevicePropertiesAttestation in the Apple MDM device
vitals datastore model so the [][]byte certificate chain is serialized to and
restored from its JSON column alongside AccessibilitySettings, OrganizationInfo,
and MDMOptions. Add the corresponding database row/schema mapping and a
round-trip test covering non-empty attestation data; otherwise remove the
field’s persisted JSON contract.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not accurate — verified against current code. newDeviceVitalsRow (server/datastore/mysql/apple_mdm_device_vitals.go:108) does marshal DevicePropertiesAttestation via the same jsonColumn helper as the other three JSON fields, and the migration's device_properties_attestation json column exists. This was also confirmed end-to-end against a real enrolled iPhone (a 2-cert JSON array round-tripped correctly). Added TestHostMDMAppleDeviceVitalsInsertThenUpdate's new round-trip assertion in 5aaa3c9 to close the coverage gap you correctly flagged, even though the persistence itself was already working.

Comment thread server/mdm/apple/commander_test.go Outdated
Comment on lines +1177 to +1178
require.Equal(t, "DeviceInformation", gotCommand.Command.RequestType)
require.Equal(t, DeviceInformationQueryKeys, gotCommand.Command.Queries)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the expected query list independent of the implementation.

Comparing the payload to DeviceInformationQueryKeys only proves that the slice was serialized. The test still passes if a required query is removed, reordered, or misspelled. Assert against a fixed expected list to protect the 29 newly added queries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/mdm/apple/commander_test.go` around lines 1177 - 1178, Update the
assertions in the DeviceInformation command test to compare
gotCommand.Command.Queries against an explicit, fixed list containing all 29
required query names in the expected order, rather than reusing
DeviceInformationQueryKeys. Keep the existing RequestType assertion unchanged.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Valid, fixed in 5aaa3c9 — assertion now compares against an explicit, independent literal list of all 35 keys instead of the package's own var.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.44770% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.03%. Comparing base (c69bb92) to head (ed30cac).

Files with missing lines Patch % Lines
server/datastore/mysql/apple_mdm_device_vitals.go 85.18% 12 Missing and 12 partials ⚠️
...s/20260727191311_CreateHostMDMAppleDeviceVitals.go 91.42% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@                     Coverage Diff                     @@
##           49984-ios-ipados-vitals   #50046      +/-   ##
===========================================================
- Coverage                    68.08%   68.03%   -0.06%     
===========================================================
  Files                         3936     3928       -8     
  Lines                       250690   250700      +10     
  Branches                     13277    13361      +84     
===========================================================
- Hits                        170687   170557     -130     
- Misses                       64691    64821     +130     
- Partials                     15312    15322      +10     
Flag Coverage Δ
backend 69.44% <87.44%> (+0.01%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

- deviceInformationQueryKeys is now unexported (nothing outside the
  package used it) to prevent external mutation of shared slice state.
- Queries assertion now compares against an explicit, independent list
  instead of the var it's meant to validate.
- Added coverage for resubmitting an identical payload (main row and a
  subscription slot) and a round-trip test for non-empty
  DevicePropertiesAttestation.
@nulmete

nulmete commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Addressing the two nitpicks from the review (no dedicated inline threads to reply to):

  • Private query list: valid, fixed in 5aaa3c9DeviceInformationQueryKeys is now unexported (deviceInformationQueryKeys); nothing outside the package referenced it.
  • Discard push-factory setup error: not applicable — newMockAPNSPushProviderFactory() returns (*factory, *provider), not (*factory, error). There's no error being discarded; the _ skips the provider, matching the same pre-existing pattern used in all 10 other tests in this file.

}

_, err = tx.Exec(`
CREATE TABLE host_mdm_apple_service_subscriptions (

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ServiceSubscriptions is another new vital which is returned as an array by Apple, so I modeled it as a separate table (e.g. dual-SIM devices report multiple subscriptions)

…command

# Conflicts:
#	server/datastore/mysql/schema.sql
Move deviceVitalsUpdateStmt/deviceVitalsInsertStmt from package-level
consts into SetOrUpdateHostMDMAppleDeviceVitals, matching the pattern
already used by replaceHostMDMAppleServiceSubscriptions in this file.

@lucasmrod lucasmrod left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great. Left some questions.

PS: We should update osquery-perf to push the new fields in DeviceInformation. (If this is on a subsequent PR then just ignore me this.)

func replaceHostMDMAppleServiceSubscriptions(ctx context.Context, tx sqlx.ExtContext, hostUUID string, subscriptions []fleet.MDMAppleServiceSubscription) error {
var existingSlots []string
if err := sqlx.SelectContext(ctx, tx, &existingSlots,
`SELECT slot FROM host_mdm_apple_service_subscriptions WHERE host_uuid = ?`, hostUUID); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: We can probably optimize later if really need be. We could get the data of slots and then compare to determine if an UPDATE is really needed at all (maybe data doesn't change that often, but we'll know after we ship and we can iterate if need be.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Makes sense to iterate on this 👍 (I'll make a change for the osquery-perf changes in a separate PR, and use that to load test to see how this update, and the other update on the other table affect.)

Comment on lines +45 to +48
accessibility_settings json DEFAULT NULL,
organization_info json DEFAULT NULL,
mdm_options json DEFAULT NULL,
device_properties_attestation json DEFAULT NULL,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are any of these big enough to warrant a load test?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Probably device_properties_attestation at a big scale, which is the longest/biggest value on the table.


// deviceInformationQueryKeys are the Apple query keys requested in a
// DeviceInformation command's <Queries> array, in request order.
var deviceInformationQueryKeys = []string{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we want to ingest these fields for personal BYOD devices?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not sure, I'll ask product/apple-at-work group and re-iterate if needed 👍

@nulmete
nulmete merged commit 0dec82b into 49984-ios-ipados-vitals Aug 4, 2026
35 checks passed
@nulmete
nulmete deleted the 49984-vitals-storage-and-command branch August 4, 2026 13:47
nulmete added a commit that referenced this pull request Aug 4, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Relates to #49984

This is PR 2 of 2 for #49984, stacked on top of PR 1 (#50046) — diff
here is scoped to just the ack-parsing/wiring work on top of that PR's
storage and command changes. This PR parses the additional
`DeviceInformation` ack fields requested by PR 1's command expansion and
persists them via `SetOrUpdateHostMDMAppleDeviceVitals`. A persistence
failure is logged rather than aborting the MDM check-in, since it's a
non-critical write the next refetch will redo.

# Checklist for submitter

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

It will be included in the feature branch.

## Testing

- [x] Added/updated automated tests

- [x] QA'd all new/changed functionality manually

Enrolled my iPhone to Fleet (dual-SIM, physical + eSIM):

<img width="1458" height="830" alt="Screenshot 2026-07-28 at 10 57
00 AM"
src="https://github.com/user-attachments/assets/1735b5e6-ac2d-4dc1-a66b-c8d8063b7c76"
/>

`host_mdm_apple_device_vitals` — populated as expected:
- `battery_level: 0.87`, `cellular_technology: 1`, all `is_*` booleans,
`last_cloud_backup_date` — parsed correctly
- `device_properties_attestation`: JSON array of **2** base64 DER certs
(leaf + "Apple Enterprise Attestation Sub CA" intermediate) — confirms
it's a cert chain, not a boolean, per the deviation noted in the parent
issue
- `mdm_options: {}` — key present, no sub-options applicable → empty
object, not `NULL` (expected `omitempty` behavior)
- `push_token: NULL` — expected; Apple only returns this for
user-channel enrollments, not device-channel
- `organization_info` / `accessibility_settings: NULL` — expected,
nothing configured/toggled
- `model_number`, `modem_firmware_version`,
`supplemental_build_version`, `bluetooth_mac`, `wifi_mac`,
`eas_device_identifier`, `itunes_store_account_hash` — all populated

`host_mdm_apple_service_subscriptions` — **2 rows** for one dual-SIM
device, confirming the multi-slot replace logic works on real hardware:
- `CTSubscriptionSlotOne` — fully populated (carrier, MCC/MNC, ICCID,
phone number, etc.)
- `CTSubscriptionSlotTwo` — only `eid`/`imei` populated, everything else
`NULL` — an inactive/unprovisioned eSIM slot reporting a sparse row,
exactly the shape the tests were built to mirror



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Apple device management now captures and stores additional iPhone and
iPad device vitals during device information refreshes.
* Supports details such as Wi‑Fi MAC address, lost mode status,
operating system information, accessibility settings, organization
details, and service subscriptions.

* **Bug Fixes**
* Missing or unexpected device information no longer interrupts refresh
processing.
* Failures saving supplementary device vitals no longer prevent other
device management actions from completing.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
nulmete added a commit that referenced this pull request Aug 4, 2026
…#50514)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Relates to #49984

Restricts device vitals fields added by #49984 (battery level, service
subscriptions/phone numbers, accessibility settings, etc.) to non-BYOD
iOS/iPadOS enrollments only. Personal (BYOD) hosts continue to get the 9
fields Fleet requested before #49984 (device name, capacity, OS version,
product name, lost-mode status, etc.), but not the newer ones, since
those can carry PII a personal device's owner may not want their
employer to see.

Note: Raised during review of #50046, see [this
comment](#50046 (review)).

## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
  * Improved support for personally enrolled iOS and iPadOS devices.
* Personally enrolled devices now receive a streamlined
device-information request.
* Vitals for personally enrolled devices display the full list without a
“View all” limit or button.
* **Bug Fixes**
* Corrected Vitals card behavior for BYOD devices while preserving
existing behavior for other hosts.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

2 participants