Skip to content

Serve custom DDM activations to devices - #50421

Merged
raju249 merged 9 commits into
mainfrom
49971-ddm-sync-custom-activations
Aug 6, 2026
Merged

raju249 merged 9 commits into
mainfrom
49971-ddm-sync-custom-activations

Conversation

@raju249

@raju249 raju249 commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Related issue: Resolves #49971

Serves the custom activations stored by #49970 to devices. Stacked on #50280.

  • The manifest advertises a custom activation's own identifier; the fetch serves its stored JSON with the predicate intact. Secrets and host variables are expanded at delivery, like configurations.
  • activation_updated_at folds into the token, so editing only a predicate re-syncs the host even though the declaration's content didn't change.
  • Management declarations are served under the manifest's Management section, have their own endpoint, and report status.
  • Predicate outcomes map per the Figma dev note: Info.Predicate → verified with an explanation, Error.ActivationFailed → failed.

Three things to look at

Generated activations are renamed to <declaration uuid>.activation. Removes the collision between an admin-chosen identifier and one Fleet generates, and collapses the fetch into a single lookup rather than match-then-trim. Note this differs from the CoS wording of "byte-for-byte identical to today".

Existing hosts will see their activations renamed once. The sync token doesn't move for the rename alone — activation identifiers aren't part of it, confirmed in MDMAppleDDMDeclarationsToken — so it happens lazily on the next real DDM change rather than all at once.

activation_updated_at is not the max() the issue describes. The issue asks for max(activation uploaded_at, latest variable change). Variable changes stay on variables_updated_at; this is GREATEST(uploaded_at, secrets_updated_at). Same outcome, since EffectiveDDMToken hashes all the timestamps together, but each signal stays independently comparable, which is what keeps the reconciler idempotent.

The token is computed in three places, not two. SQL in MDMAppleDDMDeclarationsToken, Go in EffectiveDDMToken, and a third independent copy building the manifest-level token in handleDeclarationItems. All three now carry the new term in the same order. ActivationUpdatedAtFoldsIntoToken asserts the SQL and Go agree with it populated — they only agreed for the nil case before.

Checklist for submitter

  • 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

Tested on an ADE-enrolled Mac (macOS 26.5.1) against a local server: the custom activation is advertised and fetched by its own identifier, an activation-only edit re-syncs the device even though the declaration is unchanged, a true predicate enforces the policy on the host, and a false one comes back verified with the explanation.

That turned up two bugs, both fixed here and both invisible to the test suite:

  • MDMAppleStoreDDMStatusReport computed the matching token with activation_updated_at but never selected the column, so it was always nil. Every status report was silently dropped and profiles sat in verifying forever.
  • Apple splits a predicate outcome across two arrays — the activation reports Info.Predicate, the configuration it gates reports Error.ActivationFailed. Reading only the configuration made a host the predicate excluded look failed. Now correlated via the activation identifier in the failure details.

The test payloads for the second one are the real reports the Mac sent, not hand-written.

Note for anyone testing in the UI: the OS settings tooltip for a verified profile is hardcoded to "The host applied the setting. Fleet verified." and doesn't read detail yet, so a predicate-excluded profile reads wrong there. Backend stores the right message; rendering it is #49973.

New cases in apple_mdm_ddm_test.go: the token agreement above; a custom activation advertised and served with its predicate, with the generated name no longer resolving for it; a declaration without one still getting the synthesized activation; management routing plus the endpoint type guard; the three predicate status outcomes; and a host outside a declaration's scope getting an empty manifest and an error fetching the activation by name.

13 existing assertions moved from <identifier>.activation to <uuid>.activation.

The GeneratedActivationStillServedWhenNoCustomOne case caught a real bug on its first run: the LEFT JOIN returns NULL for declarations without a custom activation, which json.RawMessage can't scan, so every non-custom activation fetch would have 500'd. That's the no-regression path.

Summary by CodeRabbit

  • New Features

    • Added Apple DDM management declarations and custom activation delivery.
    • Added host-scoped activation resolution, activation identifiers, and activation update timestamps.
    • Added support for activation variables, secrets, assets, and separate management and configuration endpoints.
  • Bug Fixes

    • Declarations are now redelivered when activation predicates change or are removed.
    • Improved status reporting for predicate exclusions, activation failures, and management declarations.
    • Improved synchronization token updates when variables, assets, or activations change.

@codecov

codecov Bot commented Aug 3, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 76.73716% with 77 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.36%. Comparing base (479c4da) to head (f645f84).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
server/service/apple_mdm.go 76.21% 32 Missing and 12 partials ⚠️
server/datastore/mysql/apple_mdm_batched.go 63.33% 13 Missing and 9 partials ⚠️
server/datastore/mysql/apple_mdm.go 91.04% 3 Missing and 3 partials ⚠️
server/mdm/apple/reconcile.go 33.33% 3 Missing and 1 partial ⚠️
server/datastore/mysql/scim.go 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #50421      +/-   ##
==========================================
+ Coverage   68.34%   68.36%   +0.01%     
==========================================
  Files        3953     3954       +1     
  Lines      253180   253644     +464     
  Branches    13530    13530              
==========================================
+ Hits       173031   173398     +367     
- Misses      64678    64728      +50     
- Partials    15471    15518      +47     
Flag Coverage Δ
backend 69.61% <76.73%> (+0.02%) ⬆️

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.

@raju249
raju249 force-pushed the 49970-ddm-activation-validation-upload branch from 5f23eb0 to 5bc4b34 Compare August 3, 2026 17:07
@raju249
raju249 force-pushed the 49971-ddm-sync-custom-activations branch from e85b0a6 to e2217c7 Compare August 3, 2026 17:08
@raju249
raju249 force-pushed the 49971-ddm-sync-custom-activations branch from e2217c7 to 3d0b76d Compare August 4, 2026 17:08
@raju249 raju249 self-assigned this Aug 5, 2026
@raju249
raju249 force-pushed the 49971-ddm-sync-custom-activations branch from 3d0b76d to 38e43c5 Compare August 5, 2026 09:08
@raju249
raju249 force-pushed the 49971-ddm-sync-custom-activations branch from 38e43c5 to c1cfa58 Compare August 5, 2026 09:21
Base automatically changed from 49970-ddm-activation-validation-upload to main August 5, 2026 09:59
@raju249
raju249 force-pushed the 49971-ddm-sync-custom-activations branch from c1cfa58 to 967d83e Compare August 5, 2026 10:06
@raju249
raju249 marked this pull request as ready for review August 5, 2026 10:07
@raju249
raju249 requested a review from a team as a code owner August 5, 2026 10:07
@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Apple DDM declarations now carry activation timestamps, identifiers, declaration types, and activation content. Effective declaration tokens include activation timestamps. Datastore queries load and persist activation metadata and resolve stored or generated activations by host and scope. The service separates configuration and management declarations, expands activation variables, validates endpoint types, and maps predicate and activation status results. Reconciliation reinstalls declarations when activation metadata changes.

Possibly related issues

Possibly related PRs

  • fleetdm/fleet#50133 — Provides the custom activation schema and storage fields used by this change.
  • fleetdm/fleet#50280 — Establishes related custom activation and management declaration persistence and service flows.
  • fleetdm/fleet#50561 — Shares Apple DDM management declaration and activation status handling changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: serving custom DDM activations to devices.
Description check ✅ Passed The description identifies the issue, explains the implementation, records testing, and documents key behavior and regressions.
Linked Issues check ✅ Passed The changes address custom activation delivery, token updates, variable expansion, scoping, synthesized activations, management declarations, and status mapping in #49971.
Out of Scope Changes check ✅ Passed The datastore, reconciliation, service, mock, and test changes support the linked issue objectives and show no unrelated code changes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 49971-ddm-sync-custom-activations

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: 1

🧹 Nitpick comments (1)
server/fleet/apple_mdm_test.go (1)

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

Add a case that covers the activation timestamp.

Every call passes nil for activationUpdatedAt, so the new branch in EffectiveDDMToken is never exercised here. The comment on Line 719 also still lists only variables_updated_at and assets_updated_at.

server/service/apple_mdm_ddm_test.go covers SQL-vs-Go parity end to end, so this is a unit-level gap only.

♻️ Proposed test additions
 	const staticToken = "abc123"
 	vars := time.Date(2026, 7, 9, 12, 0, 0, 0, time.UTC)
 	assets := time.Date(2026, 7, 10, 8, 30, 0, 0, time.UTC)
+	activation := time.Date(2026, 7, 11, 9, 15, 0, 0, time.UTC)
 
 	// md5Hex mirrors the hashing done by EffectiveDDMToken so we can assert the
 	// exact concatenation order, which MUST match the SQL token computation in
-	// MDMAppleDDMDeclarationsToken: HEX(token) + variables_updated_at + assets_updated_at.
+	// MDMAppleDDMDeclarationsToken:
+	// HEX(token) + variables_updated_at + assets_updated_at + activation_updated_at.
 	t.Run("different asset timestamps yield different tokens", func(t *testing.T) {
 		later := assets.Add(time.Second)
 		require.NotEqual(t, EffectiveDDMToken(staticToken, nil, &assets, nil), EffectiveDDMToken(staticToken, nil, &later, nil))
 	})
+
+	t.Run("only activation", func(t *testing.T) {
+		got := EffectiveDDMToken(staticToken, nil, nil, &activation)
+		require.Equal(t, md5Hex(staticToken, activation.Format(layout)), got)
+		require.NotEqual(t, staticToken, got)
+	})
+
+	t.Run("all three, order is static+vars+assets+activation", func(t *testing.T) {
+		require.Equal(t,
+			md5Hex(staticToken, vars.Format(layout), assets.Format(layout), activation.Format(layout)),
+			EffectiveDDMToken(staticToken, &vars, &assets, &activation))
+	})
🤖 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/apple_mdm_test.go` around lines 717 - 750, Add unit coverage for
the activationUpdatedAt branch in EffectiveDDMToken by updating the md5Hex
documentation and expected concatenation order to include activation_updated_at,
then add cases for activation-only and combined timestamp inputs, including a
differing activation timestamp producing a different token.
🤖 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/mdm/apple/reconcile.go`:
- Around line 250-253: Update the existing-declaration reconcile logic near the
ActivationUpdatedAt handling to detect when a custom activation is cleared, and
mark the affected host declaration as needing resync or bump its host token.
Ensure this path updates the relevant resync/token state so the host starts a
new DDM session after setMDMAppleDDMActivationDB removes the activation.

---

Nitpick comments:
In `@server/fleet/apple_mdm_test.go`:
- Around line 717-750: Add unit coverage for the activationUpdatedAt branch in
EffectiveDDMToken by updating the md5Hex documentation and expected
concatenation order to include activation_updated_at, then add cases for
activation-only and combined timestamp inputs, including a differing activation
timestamp producing a different token.
🪄 Autofix

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: 60a454bc-202e-452c-a1b7-cf1c569a548f

📥 Commits

Reviewing files that changed from the base of the PR and between 5e95589 and 967d83e.

📒 Files selected for processing (10)
  • server/datastore/mysql/apple_mdm.go
  • server/datastore/mysql/apple_mdm_batched.go
  • server/fleet/apple_mdm.go
  • server/fleet/apple_mdm_test.go
  • server/fleet/datastore.go
  • server/mdm/apple/reconcile.go
  • server/mock/datastore_mock.go
  • server/service/apple_mdm.go
  • server/service/apple_mdm_ddm_test.go
  • server/service/integration_mdm_ddm_test.go

Comment thread server/mdm/apple/reconcile.go
@raju249

raju249 commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/service/apple_mdm_ddm_test.go (1)

705-716: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the activation identifier that this declaration delivers.

Line 706 creates configIdent + ".custom". The declaration at lines 707-712 has no custom activation. It therefore uses the synthesized identifier decl.DeclarationUUID + ".activation".

This fixture does not represent a device report for the declaration under test. Use the synthesized identifier, or persist a custom activation with the .custom identifier.

Proposed fix
-				activationIdent := configIdent + ".custom"
 				decl, err := ds.NewMDMAppleDeclaration(ctx, &fleet.MDMAppleDeclaration{
 					Name:       "PredDecl-" + suffix,
 					Identifier: configIdent,
 					RawJSON:    []byte(`{"Type":"com.apple.configuration.test","Identifier":"` + configIdent + `","Payload":{"Enabled":true}}`),
 					Scope:      fleet.PayloadScopeSystem,
 				}, nil)
 				require.NoError(t, err)
+				activationIdent := decl.DeclarationUUID + ".activation"
 				token := insertHostDeclaration(t, hostUUID, decl.DeclarationUUID, "pending", "install", decl.Identifier)
🤖 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/service/apple_mdm_ddm_test.go` around lines 705 - 716, Update the test
fixture around NewMDMAppleDeclaration and c.report so activationIdent matches
the declaration’s actual activation identifier: use the synthesized
decl.DeclarationUUID + ".activation" unless the declaration is changed to
persist a custom ".custom" activation. Ensure the reported identifier represents
the activation delivered by the declaration under test.
🧹 Nitpick comments (1)
server/mdm/apple/reconcile_test.go (1)

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

Add coverage for a newer activation timestamp.

The test does not cover the d.ActivationUpdatedAt newer-than-host branch in ComputeDeclarationDeltas. Add a case where the desired timestamp advances. Assert that it creates an install row and stores the newer ActivationUpdatedAt.

🤖 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/reconcile_test.go` around lines 598 - 659, Add a subtest
near the existing activation cases that sets the desired declaration’s
ActivationUpdatedAt to a timestamp newer than the host’s value, then calls
ComputeDeclarationDeltas and asserts the device is changed, exactly one install
row is produced, and rows[0].ActivationUpdatedAt equals the newer desired
timestamp.
🤖 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.

Outside diff comments:
In `@server/service/apple_mdm_ddm_test.go`:
- Around line 705-716: Update the test fixture around NewMDMAppleDeclaration and
c.report so activationIdent matches the declaration’s actual activation
identifier: use the synthesized decl.DeclarationUUID + ".activation" unless the
declaration is changed to persist a custom ".custom" activation. Ensure the
reported identifier represents the activation delivered by the declaration under
test.

---

Nitpick comments:
In `@server/mdm/apple/reconcile_test.go`:
- Around line 598-659: Add a subtest near the existing activation cases that
sets the desired declaration’s ActivationUpdatedAt to a timestamp newer than the
host’s value, then calls ComputeDeclarationDeltas and asserts the device is
changed, exactly one install row is produced, and rows[0].ActivationUpdatedAt
equals the newer desired timestamp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 391ecb42-3937-453e-9790-262dadca4655

📥 Commits

Reviewing files that changed from the base of the PR and between 967d83e and 901522b.

📒 Files selected for processing (3)
  • server/mdm/apple/reconcile.go
  • server/mdm/apple/reconcile_test.go
  • server/service/apple_mdm_ddm_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • server/mdm/apple/reconcile.go

Comment thread server/service/apple_mdm.go Outdated

Copilot AI 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.

Pull request overview

This PR extends Fleet’s Apple DDM sync pipeline to serve custom activations and management declarations to devices, ensuring activations are advertised/fetched by their own identifiers, that activation changes trigger re-sync via tokens, and that status reporting correctly reflects predicate outcomes.

Changes:

  • Serve custom activation JSON (with predicate preserved) via a dedicated activation endpoint, expanding secrets and host/Fleet variables at delivery time.
  • Add activation_updated_at into per-host effective tokens and the manifest-level token so activation-only edits trigger re-sync.
  • Route com.apple.management.* declarations under the manifest’s Management section with a dedicated endpoint and updated status grading (including predicate correlation via activation identifiers).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/service/apple_mdm.go Manifest generation now includes Management and activation identifiers; activation/config endpoints updated; status mapping updated for predicates and management declarations.
server/datastore/mysql/apple_mdm.go SQL updated to fold activation_updated_at into token computation; declaration-items query includes activation metadata; new activation fetch query added.
server/datastore/mysql/apple_mdm_batched.go Reconciler input now loads activation timestamps and marks declarations as depending on activation Fleet vars.
server/mdm/apple/reconcile.go Reconciler now triggers installs on activation timestamp changes/removal and persists activation_updated_at on host declaration rows.
server/fleet/apple_mdm.go Data model updated: new activation_updated_at, activation delivery struct, token function signature extended, generated activation suffix constant added.
server/fleet/datastore.go Datastore interface extended with MDMAppleDDMActivationResponse.
server/mock/datastore_mock.go Datastore mock extended to support MDMAppleDDMActivationResponse.
server/service/apple_mdm_ddm_test.go Adds coverage for token agreement, custom activation serving/scoping, management routing/guard, and predicate status mapping.
server/mdm/apple/reconcile_test.go Adds tests ensuring activation changes/removal trigger reconcile deltas appropriately.
server/fleet/apple_mdm_test.go Updates EffectiveDDMToken tests and HostDeclaration.Equal tests for the new activation timestamp field.
server/service/integration_mdm_ddm_test.go Updates integration test expectations for the expanded EffectiveDDMToken signature.

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

Comment thread server/service/apple_mdm.go Outdated
Comment thread server/datastore/mysql/apple_mdm_batched.go

@MagnusHJensen MagnusHJensen 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.

A couple of things to look at here to further perfect it, but else overall looking good.

Comment thread server/service/apple_mdm.go Outdated
Comment thread server/datastore/mysql/apple_mdm.go Outdated
Comment thread server/service/apple_mdm.go
Comment thread server/fleet/apple_mdm.go Outdated
Comment thread server/service/apple_mdm.go Outdated
Comment thread server/service/apple_mdm.go Outdated
Comment thread server/datastore/mysql/apple_mdm_batched.go
@raju249

raju249 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for another round @MagnusHJensen

I have addressed the copilot and your feedback points. Can you take a look, please?

Comment thread server/service/apple_mdm.go Outdated
Comment thread server/service/apple_mdm.go
Comment thread server/service/apple_mdm.go Outdated

@MagnusHJensen MagnusHJensen 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.

We currently still have two issues in the entire flow.

IDP variable resend triggers does not actually check on activation variable association and therefore does not stamp the host declaration row for resend in the reconciler to fire a new IDP variable updated profile. Method triggerResendProfilesUsingVariables

Host vitals resend is also not accounting for activations. Method resendMDMProfilesForCustomHostVital

Sorry for doing these in chunks. There is a lot of mental modal to keep here and various paths to exercise so I'm doing them bit by bit when I find them.

Comment thread server/service/apple_mdm.go
@raju249

raju249 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

No worries @MagnusHJensen

I am able to build the context and build the mental model myself, so all good there.

I have addressed the feedback, can you take a look, please?

@MagnusHJensen

MagnusHJensen commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

I am able to build the context and build the mental model myself, so all good there.

Can you clarify? I'm unsure what this means in response to my message

@raju249

raju249 commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Sorry it was not clear.

The response was for -

Sorry for doing these in chunks. There is a lot of mental modal to keep here and various paths to exercise so I'm doing them bit by bit when I find them.

As in, you don't need to be sorry 😅 The reviews are helping me even if they are in chunks

I have made the changes as per your review. But the review in turn is helping me to look at the entry points that I missed earlier like IdP changes, host vital changes.

raju249 added 9 commits August 6, 2026 19:50
Makes the stored activations from #49970 actually reach hosts.

The manifest advertises a custom activation's own identifier instead of the
generated one, and the fetch serves the stored JSON with its predicate.
Secrets and host variables are expanded at delivery like configurations.

Generated activations are now named after the declaration UUID rather than
its identifier, so an admin-chosen identifier can't collide with one Fleet
invents. That means one lookup instead of a match-then-trim fallback. Note
this differs from the CoS wording of "byte-for-byte identical to today" —
following Magnus's Slack instead, same as we did for base64.

activation_updated_at folds into the token in all three places it's computed
(SQL, EffectiveDDMToken, and the manifest builder), so editing a predicate
re-syncs even though the declaration itself didn't change. Sourced from
GREATEST(uploaded_at, secrets_updated_at) so a secret re-stamp counts too.
Kept variables on variables_updated_at rather than folding them in here, so
each signal stays independently comparable and the reconciler stays
idempotent.

Management declarations are served under the manifest's Management section
with their own endpoint, and report status.

Predicate outcomes map per the Figma dev note: Info.Predicate is verified
with an explanation, Error.ActivationFailed is failed.
The status report query didn't select activation_updated_at, so the token
Fleet computed for matching never included it while the one it served did.
Every status report was silently dropped and profiles sat in verifying
forever. Checked the other three EffectiveDDMToken call sites too, they were
fine.

Apple splits a predicate outcome across two arrays: the activation reports
Info.Predicate, the configuration it gates reports Error.ActivationFailed.
We only read the configuration, so a host the predicate simply excluded came
back as failed. Now correlated via the activation identifier Apple puts in
the failure details.

Test payloads are the real reports a macOS 26 host sent rather than
hand-written ones, since both bugs came from guessing at the shape.
A null document would otherwise leave it nil and panic on write.
The delta only looked for a newer activation timestamp, so deleting one
left the host serving the old activation and its predicate indefinitely.
Unlike an asset reference, the activation lives in its own table, so the
declaration's token doesn't change to catch it.
Apple documents that a management declaration's active state is always
false and not part of the activation process, so grading it like a
configuration left every one of them stuck on verifying. Found testing
com.apple.management.organization-info on a real Mac.
- Grade configurations and management declarations in separate passes, and
  fail a management declaration that reports errors while still unknown
- Serve a custom activation under its own token, so editing the declaration
  or an asset it references no longer re-syncs the activation
- Include management declarations when resolving referenced assets
- Preflight the activation for Fleet variables and scan it for host vitals
- Fix swapped declaration_name/declaration_identifier and add the missing
  scope on the host declaration insert
The manifest carried the activation's identifier, token and body as extra
columns on every declaration row, which made the activation's variables
get checked against the declaration's variables_updated_at. A variable
that lived only in the activation was therefore never detected, so it was
never preflighted and its value never re-synced.

Custom activations now come from their own query, and the manifest is
built in three passes: installable declarations, activations, then
configurations and management.
The activation's own token only covers its content and secrets, so a new
variable value moved the declaration's token but not the activation's --
the host re-fetched the configuration and kept the stale activation. Fold
the declaration's variables_updated_at into it, on both the manifest and
delivery sides.

The IdP and host vital resend triggers had the same blind spot: neither
looked at an activation's variables, so a value change never marked the
declaration for resend.
@raju249
raju249 force-pushed the 49971-ddm-sync-custom-activations branch from bec6f0b to f645f84 Compare August 6, 2026 14:29

@JordanMontgomery JordanMontgomery 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.

I'm not really finding any issues and I think this is good to go. I was most worried about the reconciler/generated activation identifier changes but digging in I think they look good

This branch was previously deployed

1 inactive deployment
Docker Hub — f645f849 Deployed Aug 6, 2026 by raju249 via publish #99719
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.

CADDM: DDM Sync + Reconciler

4 participants