<!-- Add the related story/sub-task/bug number, like Resolves#123, or
remove if NA -->
**Related issue:** Resolves #
Raised by the frontend while building the Edit modal: there was no way
to clear a declaration's custom activation. An absent `activation` field
meant "keep it" on a labels-only edit but "delete it" when the profile
contents were replaced, so clearing wasn't expressible and an ordinary
content edit silently dropped the activation.
The field is now three-state:
| Request | Result |
|---|---|
| no `activation` key | stored activation left alone |
| `activation` as an empty value | removed |
| `activation` as a file | replaced |
Multipart has no null, so an empty value stands in for one.
Note this changes one existing behaviour: replacing a profile's contents
without sending an activation used to delete it, and now preserves it.
Removal has to be explicit.
Anything ambiguous is rejected rather than guessed at, since every
ambiguous form would otherwise resolve to deleting the stored
activation:
| Request | Result |
|---|---|
| `activation` as a nonempty value | 422 — more likely a malformed
upload than a request to delete |
| `activation` as a zero-byte file | 422 — a failed upload shouldn't
delete anything |
| `activation` sent as both a file and a value | 422 — one says replace,
the other says remove |
The unsupported-profile check also keys on the field being present
rather than on it carrying content, so clearing an activation on a
Windows, Android or mobileconfig profile is rejected instead of quietly
succeeding.
On the datastore side, `SetOrUpdateMDMAppleDeclaration` now takes an
explicit action (`MDMAppleActivationKeep` / `MDMAppleActivationApply`)
instead of inferring intent from the struct. The write is a full
replace, so "keep" has to be stated — otherwise preserving the
activation would mean reading it back and handing it to the write, which
also risked dropping its Fleet variable associations. As a side effect
the OS updates cron no longer fires a DELETE for an activation it never
had.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [ ] 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.
- [x] 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.
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes
## Testing
- [x] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually
Integration test covers all three states end to end through the
multipart decoder, plus service-level tests for preserve and explicit
removal.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Apple MDM declaration updates now support preserving, replacing, or
explicitly removing activation settings.
* Omitted activation fields leave existing settings unchanged, while
empty fields remove them.
* Apple OS update declarations retain activation settings by default.
* **Bug Fixes**
* Labels-only updates no longer unintentionally carry forward activation
data.
* Invalid, empty, or conflicting activation uploads now receive clear
validation errors.
* Unsupported profile types now reject activation updates.
* **Tests**
* Added coverage for activation preservation, replacement, removal, and
integration scenarios.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->