Files
fleet/server/datastore
Rajendra Kadam 9d0f510a8d Add DDM custom activations schema (#50133)
**Related issue:** Resolves #49966

Adds the schema for custom DDM activations (parent story #48222).

- **Creates `mdm_apple_ddm_activations`** — stores the activation JSON
as-is (`mediumtext`, so the generated `token` column hashes the exact
stored bytes) with a `declaration_uuid` FK to `mdm_apple_declarations`
that cascades on delete.
- **Extends `mdm_configuration_profile_variables`** with
`apple_ddm_activation_uuid` so activations can carry Fleet variables
(needed by #49970).
- **Adds `activation_updated_at`** to `host_mdm_apple_declarations` so a
changed activation regenerates the declaration's effective token,
mirroring `variables_updated_at` / `assets_updated_at`.
- **Drops `mdm_apple_declaration_activation_references`** — created with
the original DDM tables in `20240327115530_AddDDMTables.go`, never
written to by any code path, so it is empty in every deployment.

### Deviations from the SQL in #49966

The `declaration_uuid` FK is the one addition, [confirmed with
@MagnusHJensen](https://github.com/fleetdm/fleet/issues/49966): it keeps
the 1:1 lifecycle enforced by the database rather than requiring cleanup
in every delete path. `configuration_identifier` is kept alongside it
for validation and DDM serving. Its unique key doubles as the FK's
backing index.

The rest are corrections needed for the specced SQL to work, all
following the precedent in `20260409153715_AddDDMVariablesSupport.go`:

- **`ck_mdm_configuration_profile_variables_exactly_one` is dropped and
re-added** to count the new column. That constraint requires exactly one
owner column to be non-null; adding a seventh without updating it means
any row setting `apple_ddm_activation_uuid` sums to 0, fails the check,
and is rejected.
- **`UNIQUE (apple_ddm_activation_uuid, fleet_variable_id)` added** to
match the six existing owner columns. That table's write path is `INSERT
... ON DUPLICATE KEY UPDATE`, which needs a unique key to collide on.
- **`activation_updated_at` is `DATETIME(6)`, not `TIMESTAMP(6)`** — its
siblings are `datetime(6)` and `EffectiveDDMToken` formats them into the
token string, so `TIMESTAMP`'s session-timezone conversion on read would
change tokens and re-push declarations to every host.
- **`team_id` gets `DEFAULT '0'`** to match `mdm_apple_declarations`,
where 0 is Unassigned.

### Note for #49970

`declaration_uuid` is `NOT NULL`, so the upload path must populate it in
addition to `configuration_identifier`. The declaration UUID prefix has
no separator (`MDMAppleDeclarationUUIDPrefix = "d"`, 1 char + 36-char
UUID = the full `varchar(37)`).

# Checklist for submitter

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

No changes file: this sub-task adds schema only and ships no
user-visible behavior.

## Testing

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

`TestUp_20260729115013` covers: the stale table is present before and
gone after; pre-existing `mdm_configuration_profile_variables` rows
survive the check constraint replacement (that `ADD CONSTRAINT`
revalidates every existing row); an activation attaches to a declaration
and gets its generated token; the 1:1 unique key and the FK both reject
bad inserts; a variable row binds to an activation (the case the old
constraint would have rejected); the constraint still rejects two-owner
and zero-owner rows; and deleting the declaration cascades to the
activation and through it to the activation's variable rows.

Also ran the full migrations suite (`MYSQL_TEST=1 go test
./server/datastore/mysql/migrations/...`) to confirm no other migration
is disturbed, and verified the regenerated `schema.sql` diff contains
only changes from this migration.

## Database migrations

- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).

Neither modified table has an `ON UPDATE CURRENT_TIMESTAMP` column, so
no rows have their timestamps touched.


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

## Summary by CodeRabbit

* **New Features**
  * Added support for Apple DDM custom activations.
* Added activation-specific tokens and timestamps to support reliable
declaration updates.
* Enabled configuration variables to be associated with a specific
activation.
* Added validation to prevent duplicate or invalid activation
associations.
* Activations and related settings are now automatically removed when
their declaration is deleted.

* **Tests**
* Added coverage for activation creation, uniqueness, validation,
associations, token generation, and cascading cleanup.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-31 14:36:15 +05:30
..