Add Android support for custom host vitals - #49696
Conversation
Custom host vitals ($FLEET_HOST_VITAL_<id>) previously worked in scripts and Apple/Windows profiles but were rejected at upload for Android configuration profiles and managed app configuration, to keep parity with $FLEET_SECRET_. This adds validation, per-host expansion at delivery, and resend-on-value- change for Android, mirroring the existing Apple/Windows behavior. Also fixes a pre-existing gap where Android profiles weren't covered by the vital delete-protection check that Apple/Windows/scripts already had, and renames ContainsCustomHostVitalIDs to FindCustomHostVitalIDs for clarity.
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Adds Android parity for custom host vitals ($FLEET_HOST_VITAL_<id>) by allowing references in Android configuration profiles and Android managed app configuration, validating referenced vitals at upload, and expanding them per-host at delivery time (including resend-on-change behavior for Android profiles).
Changes:
- Add
$FLEET_HOST_VITAL_<id>detection + per-host expansion for Android managed app config and Android profile delivery. - Validate referenced custom host vitals for Android profiles/app configs at upload (and adjust Android-side validation rules for token placement).
- Extend “used-by” scanning and “resend on vital value change” behavior to include Android configuration profiles; add/extend tests across service, profiles, fleet validation, and datastore.
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| changes/49421-android-custom-host-vitals | Release note entry (content excluded by policy). |
| server/worker/software_worker.go | Detect host-vital tokens in Android app configs and pass HostID for per-host expansion. |
| server/service/microsoft_mdm.go | Switch Windows per-host processing detection to FindCustomHostVitalIDs. |
| server/service/mdm.go | Validate referenced custom host vitals for Android profiles at upload; include Android in batch validation. |
| server/service/mdm_test.go | Add service-level tests for Android profile vital validation (contains a compile issue noted in comments). |
| server/service/custom_host_vitals_resolution_test.go | Update tests to renamed FindCustomHostVitalIDs. |
| server/service/apple_mdm.go | Update host-vital detection to FindCustomHostVitalIDs. |
| server/mdm/profiles/android_appconfig.go | Expand custom host vitals in Android managed app configuration using host numeric ID. |
| server/mdm/profiles/android_appconfig_test.go | Add tests for Android app-config host-vital substitution and missing-value behavior. |
| server/mdm/microsoft/profile_variables.go | Update host-vital detection to FindCustomHostVitalIDs. |
| server/mdm/apple/profile_processor.go | Update host-vital detection to FindCustomHostVitalIDs. |
| server/mdm/android/service/profiles.go | Add Android profile per-host expansion detection for host vitals; surface missing-vital-value failures as profile failures. |
| server/mdm/android/service/profiles_test.go | Add coverage for missing host-vital value failure path and new helper functions. |
| server/fleet/custom_host_vitals.go | Rename token finder to FindCustomHostVitalIDs; add Android profile entity constant; adjust error strings. |
| server/fleet/custom_host_vitals_test.go | Rename tests to match FindCustomHostVitalIDs. |
| server/fleet/android.go | Allow host-vital tokens in Android profile validation; enforce “must be in JSON string value”; reject malformed host-vital refs in app config validation. |
| server/fleet/android_test.go | Add/adjust Android validation tests for host-vital acceptance and malformed refs. |
| server/datastore/mysql/custom_host_vitals.go | Include Android profiles in “used-by” scan and resend-on-vital-change logic; update to FindCustomHostVitalIDs. |
| server/datastore/mysql/custom_host_vitals_test.go | Add tests ensuring Android profiles block deletion and resend-on-change resets Android profile status. |
| ee/server/service/vpp.go | Validate referenced custom host vitals in Android app configuration during VPP batch association. |
| ee/server/service/vpp_test.go | Add tests for malformed and unknown-vital references in Android app configuration. |
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (4)
WalkthroughAndroid configuration profiles and managed app configurations now accept custom host vital references, validate malformed, missing, and JSON-key usages, and expand values per host during delivery. Android substitution failures are classified for profile status reporting. Upload and batch paths validate referenced vitals. Deletion protection and resend logic now include Android profiles. Shared vital ID extraction was renamed to Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/mdm/android/service/profiles.go (1)
746-752: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftJSON-escape custom host vital values before Android substitution.
SubstituteFleetVarsInAndroidAppConfiguses JSON-safe replacement for Fleet variables but delegates vital expansion to the rawExpandCustomHostVitalspath. A value containing"or\can corrupt the managed-configuration JSON, causing the later unmarshal to fail and aborting reconciliation for the host. Make Android vital expansion JSON-context-aware and add a special-character regression test.🤖 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/android/service/profiles.go` around lines 746 - 752, Update the Android profile substitution flow around SubstituteFleetVarsInAndroidAppConfig so custom host vital expansion escapes values for JSON string context before insertion, matching Fleet variable replacement behavior and preserving valid managed-configuration JSON. Add a regression test covering vital values containing quotes and backslashes, verifying substitution and subsequent unmarshalling succeed.
🧹 Nitpick comments (1)
ee/server/service/vpp.go (1)
373-387: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winN+1 datastore round-trips for vitals validation in batch Android app association.
svc.ds.ValidateReferencedCustomHostVitalsis invoked once per Android payload inside the per-payload loop, causing one DB round trip per app when validating referenced vitals.server/service/mdm.go'sBatchSetMDMProfilesinstead aggregates all profile documents and validates them in a single call before processing individual profiles. For GitOps/batch uploads with many Android apps referencing vitals, this diverges from that batched pattern and adds N round trips instead of 1.Consider collecting
payload.Configurationfor all Android payloads up front and callingValidateReferencedCustomHostVitalsonce, mirroring themdm.goapproach.🤖 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 `@ee/server/service/vpp.go` around lines 373 - 387, Batch Android custom-host-vitals validation instead of calling ValidateReferencedCustomHostVitals inside the per-payload switch in the Android association flow. Collect all non-nil payload.Configuration values up front, invoke the datastore validation once before processing individual payloads, and preserve the existing configuration and error handling behavior.
🤖 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/custom_host_vitals.go`:
- Around line 155-159: Update the documentation comment for the custom host
vital reference-checking function to enumerate every entity type it scans:
scripts, Apple declarations and profiles, Windows and Android configuration
profiles, software installers, setup-experience scripts, and labels. Keep the
existing return-value and database-error descriptions unchanged.
---
Outside diff comments:
In `@server/mdm/android/service/profiles.go`:
- Around line 746-752: Update the Android profile substitution flow around
SubstituteFleetVarsInAndroidAppConfig so custom host vital expansion escapes
values for JSON string context before insertion, matching Fleet variable
replacement behavior and preserving valid managed-configuration JSON. Add a
regression test covering vital values containing quotes and backslashes,
verifying substitution and subsequent unmarshalling succeed.
---
Nitpick comments:
In `@ee/server/service/vpp.go`:
- Around line 373-387: Batch Android custom-host-vitals validation instead of
calling ValidateReferencedCustomHostVitals inside the per-payload switch in the
Android association flow. Collect all non-nil payload.Configuration values up
front, invoke the datastore validation once before processing individual
payloads, and preserve the existing configuration and error handling behavior.
🪄 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: 86f0b3f7-1227-4015-8c02-2c8703920d19
📒 Files selected for processing (21)
changes/49421-android-custom-host-vitalsee/server/service/vpp.goee/server/service/vpp_test.goserver/datastore/mysql/custom_host_vitals.goserver/datastore/mysql/custom_host_vitals_test.goserver/fleet/android.goserver/fleet/android_test.goserver/fleet/custom_host_vitals.goserver/fleet/custom_host_vitals_test.goserver/mdm/android/service/profiles.goserver/mdm/android/service/profiles_test.goserver/mdm/apple/profile_processor.goserver/mdm/microsoft/profile_variables.goserver/mdm/profiles/android_appconfig.goserver/mdm/profiles/android_appconfig_test.goserver/service/apple_mdm.goserver/service/custom_host_vitals_resolution_test.goserver/service/mdm.goserver/service/mdm_test.goserver/service/microsoft_mdm.goserver/worker/software_worker.go
| // Apple declarations, Windows configuration profiles, and Android configuration | ||
| // profiles for a $FLEET_HOST_VITAL_<id> (or ${FLEET_HOST_VITAL_<id>}) reference | ||
| // to the given vital id. It returns a *fleet.CustomHostVitalUsedInfo describing | ||
| // the first referencing entity found, or nil if unreferenced. Mirrors the scan | ||
| // structure of DeleteSecretVariable. The second return is a real DB error. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Keep the delete-protection comment aligned with the implementation.
The comment lists only Apple declarations, Windows profiles, and Android profiles, but this function also scans scripts, Apple profiles, software installers, setup-experience scripts, and labels. Update the comment so maintainers understand the complete protection surface.
🤖 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/custom_host_vitals.go` around lines 155 - 159, Update
the documentation comment for the custom host vital reference-checking function
to enumerate every entity type it scans: scripts, Apple declarations and
profiles, Windows and Android configuration profiles, software installers,
setup-experience scripts, and labels. Keep the existing return-value and
database-error descriptions unchanged.
There was a problem hiding this comment.
Fixed — updated the comment to list all seven scanned entity types (scripts, Apple profiles/declarations, Windows/Android profiles, software installers, setup-experience scripts) plus the separately-checked host-vitals labels.
…-custom-host-vitals # Conflicts: # server/service/mdm.go
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated 1 comment.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
| contents := string(rawJSON) | ||
|
|
||
| // Custom host vitals ($FLEET_HOST_VITAL_<id>) are validated for existence | ||
| // (and malformed refs are rejected) at the service layer via | ||
| // ds.ValidateReferencedCustomHostVitals, same as Apple/Windows profiles. | ||
| // Here we only enforce that the token sits inside a JSON string value, | ||
| // mirroring the $FLEET_VAR_* check below, since a token used as a JSON key | ||
| // would corrupt the profile structure once substituted at delivery time. | ||
| vitalIDs := FindCustomHostVitalIDs(contents) |
There was a problem hiding this comment.
Good catch on the underlying issue, though the specific premise (fleetctl client-side validation) doesn't apply here — MDMAndroidConfigProfile.ValidateUserProvided is only ever called server-side, always paired with ds.ValidateReferencedCustomHostVitals in the same request, so there wasn't an actual client/server UX mismatch today. Still worth fixing for defense-in-depth: this function was silently treating a malformed ref as "not a vital reference" and relying entirely on the caller to catch it separately. Fixed by rejecting malformed refs directly here too, mirroring ValidateAndroidAppConfiguration.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #49696 +/- ##
==========================================
+ Coverage 67.90% 68.01% +0.10%
==========================================
Files 3905 3897 -8
Lines 249645 249800 +155
Branches 13336 13118 -218
==========================================
+ Hits 169532 169896 +364
+ Misses 64838 64600 -238
- Partials 15275 15304 +29
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… directly validateAndroidProfileFleetVariables previously relied on callers pairing it with ds.ValidateReferencedCustomHostVitals to catch a malformed reference (e.g. $FLEET_HOST_VITAL_asset_tag); a malformed token was otherwise silently treated as "not a vital reference" and passed through. Reject it directly here instead, mirroring ValidateAndroidAppConfiguration, so this doesn't depend on caller ordering.
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated no new comments.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
…-custom-host-vitals # Conflicts: # server/datastore/mysql/custom_host_vitals.go
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
…tion The single-profile-upload (NewMDMAndroidConfigProfile) and Android VPP app config (BatchAssociateVPPApps) call sites for ValidateReferencedCustomHostVitals were reporting any error as a 422 invalid-argument, including a genuine datastore failure. Use fleet.IsInvalidReferencedCustomHostVitalsError to only convert the two known validation error types, matching the pattern already used at every other ValidateReferencedCustomHostVitals call site (introduced in #49586).
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
| // findCustomHostVitalTokens returns the full $FLEET_HOST_VITAL_<id> tokens | ||
| // (prefix included) found in s, in the same shape variables.Find returns | ||
| // $FLEET_VAR_* names, so both can drive walkJSONForVars. |
There was a problem hiding this comment.
Seems worth fixing the doc string to avoid confusion. They behave different.
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
| } | ||
|
|
||
| subHost := profiles.AndroidAppConfigSubstitutionHost{ | ||
| HostID: h.ID, | ||
| UUID: h.UUID, | ||
| HardwareSerial: h.HardwareSerial, | ||
| Platform: h.Platform, |
There was a problem hiding this comment.
I wouldn't like to extend the potential blast radius by touching the worker's code. This can be a follow-up if needed, IMO.
NewMDMAndroidConfigProfile wraps any error from ValidateUserProvided with a hardcoded "Couldn't add. " prefix. The custom host vitals messages in validateAndroidProfileFleetVariables already had their own "Couldn't edit profile. " prefix baked in, so uploading e.g. $FLEET_HOST_VITAL_asset_tag produced "Couldn't add. Couldn't edit profile. Invalid custom host vital reference...". Drop the redundant prefix from the vitals-specific messages so the caller's prefix applies cleanly. The existing $FLEET_VAR_* messages have the same pre-existing issue but are left alone here to keep this PR scoped to custom host vitals.
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
…/edit AddAppStoreApp/UpdateAppStoreApp never called ds.ValidateReferencedCustomHostVitals, so a managed app config referencing a non-existent $FLEET_HOST_VITAL_<id> was silently accepted, unlike the GitOps batch path which already checked it. Also addresses a review comment on PR #49696: the shared "does this content reference a Fleet var or custom host vital" check now does a cheap bytes.Contains prefix check before falling back to the string conversion FindCustomHostVitalIDs needs, avoiding that allocation in the common case where neither is present.
| if err := svc.ds.ValidateReferencedCustomHostVitals(ctx, []string{string(payload.Configuration)}); err != nil { | ||
| if !fleet.IsInvalidReferencedCustomHostVitalsError(err) { |
There was a problem hiding this comment.
This is a common pattern when validating referenced custom host vitals across the codebase. I might look into abstracting this into a helper as a follow-up.
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 22 out of 23 changed files in this pull request and generated 2 comments.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
Comments suppressed due to low confidence (2)
server/fleet/android.go:175
- The custom host vital JSON-position error message is missing the "Couldn't edit profile." prefix used by the Fleet variable validation errors in this same function, making the returned errors inconsistent.
for _, id := range vitalIDs {
token := fmt.Sprintf("%s%d", CustomHostVitalPrefix, id)
if _, inKey := vitalKeyVars[token]; inKey {
return fmt.Errorf("Custom host vital $%s must be inside a JSON string value.", token)
}
server/fleet/android.go:178
- Same as above: the custom host vital JSON-position error should likely use the same "Couldn't edit profile." prefix as the other validation errors returned from this function.
if _, inStr := vitalStringVars[token]; !inStr {
return fmt.Errorf("Custom host vital $%s must be inside a JSON string value.", token)
}
| // ContainsFleetVarOrCustomHostVital reports whether content has a $FLEET_VAR_* | ||
| // token or a $FLEET_HOST_VITAL_<id> token. Checks bytes for the vital prefix | ||
| // before falling back to fleet.FindCustomHostVitalIDs, which needs a string, to | ||
| // avoid that conversion's allocation in the common case where content has neither. | ||
| func ContainsFleetVarOrCustomHostVital(content []byte) bool { | ||
| if variables.ContainsBytes(content) { | ||
| return true | ||
| } | ||
| if !bytes.Contains(content, []byte(fleet.CustomHostVitalPrefix)) { | ||
| return false | ||
| } | ||
| return len(fleet.FindCustomHostVitalIDs(string(content))) > 0 | ||
| } |
lucasmrod
left a comment
There was a problem hiding this comment.
Looks good! Left some questions/comments.
| // at delivery time. | ||
| vitalIDs := FindCustomHostVitalIDs(contents) | ||
|
|
||
| found := variables.Find(contents) |
There was a problem hiding this comment.
Nit rename found to variables?
| return fmt.Errorf("Custom host vital $%s must be inside a JSON string value.", token) | ||
| } | ||
| if _, inStr := vitalStringVars[token]; !inStr { | ||
| return fmt.Errorf("Custom host vital $%s must be inside a JSON string value.", token) |
There was a problem hiding this comment.
Do we have test coverage for this specific error? (E.g. seems this covers on a value but not a string?)
| // findCustomHostVitalTokens returns the full $FLEET_HOST_VITAL_<id> tokens | ||
| // (prefix included) found in s, in the same shape variables.Find returns | ||
| // $FLEET_VAR_* names, so both can drive walkJSONForVars. |
There was a problem hiding this comment.
Seems worth fixing the doc string to avoid confusion. They behave different.
There was a problem hiding this comment.
Should we rename now that it's also replacing custom host vitals?
- Rename local var found -> varNames to avoid confusion with the variables package (kept distinct from it to avoid a name collision). - Drop the redundant "Couldn't edit profile." prefix from the $FLEET_VAR_* messages in validateAndroidProfileFleetVariables too, matching the earlier fix for custom host vital messages, since the caller (parseAndValidateAndroidConfigProfile) already prefixes with "Couldn't add. "/"Couldn't edit profile. " itself. - Add test coverage for a custom host vital token that ends up neither a decoded JSON key nor a decoded string value (shadowed by a duplicate JSON key), exercising that branch distinctly from the "used as a key" case. - Fix the findCustomHostVitalTokens doc comment, which incorrectly implied its output has the same shape as variables.Find's. - Rename SubstituteFleetVarsInAndroidAppConfig to SubstituteFleetVarsAndVitalsInAndroidAppConfig now that it also substitutes custom host vitals, not just $FLEET_VAR_*.
There was a problem hiding this comment.
Warning
- Copilot's review of this pull request may be incomplete because some of the changed files are excluded by your Copilot content exclusion settings. See Excluding content from Copilot for details.
Pull request overview
Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.
Files excluded by content exclusion policy (1)
- changes/49421-android-custom-host-vitals
Comments suppressed due to low confidence (2)
server/mdm/profiles/android_appconfig.go:146
ContainsFleetVarOrCustomHostVitalaims to avoid allocations, but[]byte(fleet.CustomHostVitalPrefix)allocates (string→[]byte copy) on every call. Since this helper is used on hot paths, precompute the prefix bytes once at package scope and reuse it.
func ContainsFleetVarOrCustomHostVital(content []byte) bool {
if variables.ContainsBytes(content) {
return true
}
if !bytes.Contains(content, []byte(fleet.CustomHostVitalPrefix)) {
server/fleet/android.go:134
- Returning
errors.New((&InvalidCustomHostVitalRefError{...}).Error())drops the concrete error type, preventing callers from detecting malformed vital refs viaerrors.As(e.g.fleet.IsInvalidReferencedCustomHostVitalsError). Return the typed error directly to keep it inspectable and avoid an extra string allocation.
if malformed := ContainsMalformedCustomHostVitalRefs(contents); len(malformed) > 0 {
return errors.New((&InvalidCustomHostVitalRefError{Refs: malformed}).Error())
}
Related issue: Resolves #49421
Custom host vitals (
$FLEET_HOST_VITAL_<id>) already worked in scripts and Apple/Windows configuration profiles, but Android configuration profiles and managed app configuration explicitly rejected them at upload to keep parity with$FLEET_SECRET_*. This left admins unable to inject per-host vitals (e.g. an asset tag) into Android MDM configuration the same way they can for every other platform.For more context, prior PRs:
Checklist for submitter
changes/,orbit/changes/oree/fleetd-chrome/changes.See Changes files for more information.
Testing
Added/updated automated tests
QA'd all new/changed functionality manually
Created an "Asset tag" host vital.
Enrolled an Android device.
Initially the test profile showed as "Failed" because no value was set for the vital.
Set a value for the vital, saw that it went from Enforcing to Verified.
Also tested the rejection cases:
Screen.Recording.2026-07-24.at.9.30.20.AM.mov
Summary by CodeRabbit
Summary by CodeRabbit
New Features
$FLEET_HOST_VITAL_<id>).Bug Fixes