Published 2026-10-25 · LuckyMDM Blog
Bottom line: declarative device management and the older MDM protocol are not two versions of the same interface. They are two control models. The older model sends commands that say what to do; the declarative model registers declarations that say what state the device should hold. There is no automatic translator between the two, so the cost of a migration does not sit on the server-upgrade side. It sits in rewriting configuration items one by one, rebuilding the status subscription list, and maintaining both channels for as long as they run in parallel. How long that parallel period lasts depends on how many configuration items still have no declarative equivalent, not on what week the project plan says.
Under the classic protocol, the server sends a command when it wants something done, and sends a query command when it wants to know a value, then waits for the response. A command is a one-off description of an action. Once it has been executed it is finished. The server does not hold a record of what the state *should* be; it holds a record of what it sent and what came back.
That is the underlying reason state drift goes unnoticed. If a device restarts, if someone changes a setting locally, if a value is overwritten by another profile, the server has no way to know until it asks again.
Declarative management inverts this. Instead of issuing commands, the server registers declarations describing the state a device should be in, and the device maintains that state on its own. When something changes, the device reports it back through the status channel. Where the server cares about a field, it subscribes to that field, and the device reports on subscription rather than being polled repeatedly.
This difference drives every cost that follows. A command is momentary; a declaration persists. Turning momentary actions into persistent state means the configuration has to be written again from scratch, and that is a rewrite, not a rename.
There is no automatic translator because the two models do not describe the same thing. The input to the command model is an action; the input to the declarative model is a state. From a restriction such as “camera not allowed” you can derive a command to disable the camera. The reverse does not hold: from the fact that a disable-camera command was once sent, you cannot derive that the device's desired state is camera-disabled, because someone may have changed it since, another profile may have overridden it, or the device may never have received it.
So a migration cannot be an export-and-import step. It is a per-item judgement: which declaration does this configuration map to, what is the expected value, which declaration owns it, what validity period applies. That judgement is where the hours go, and it cannot be automated away.
The first task in a migration is an inventory, not a rewrite. Sorting items into the wrong bucket causes rework: an item assumed to have a declarative equivalent turns out not to, and the regression testing already done for it is wasted.
| Bucket | How to identify it | What to do | Effort per item | Main risk |
|---|---|---|---|---|
| Has a declarative equivalent | The item appears in the currently published declaration list and the fleet meets the minimum OS version | Rewrite as a declaration; retire the item from the old channel | About 15 minutes including on-device regression | Expected-value syntax differs; a mistake shows up as an inverted restriction |
| No equivalent yet | No matching declaration, or the minimum OS version is above part of the fleet | Keep it on the old channel, tag it as dual-run, review monthly | About 20 minutes to tag and set up a regression check | Reviews lapse and the dual-run pile never shrinks |
| Depends on supervised state | A declaration exists but requires the device to be supervised | Confirm supervision first, then rewrite; unsupervised units stay in bucket two | About 25 minutes including the supervision check | Declarations silently do not apply, which reads as a migration failure |
The second bucket is what determines the length of the parallel period. The first bucket can be closed as soon as it is rewritten, and the third closes once supervision is confirmed. Only the second bucket keeps the old channel alive.
The formula is item count multiplied by per-item effort, and per-item effort must include on-device regression. Leaving regression out roughly halves the estimate. A rewrite is not a field rename: it is writing the expected value again, setting the validity period again, deciding which declaration owns it, and then confirming on a test unit that it actually took effect.
Fields that used to come back from a device-information query — device details, installed applications, OS version, available capacity, activation lock state — have to be re-registered as status subscriptions. Subscriptions are not the same thing as configuration: configuration is what the server requires of the device, subscriptions are what the server asks the device to report. Legacy systems tend to mix both inside one query command, so the migration has to separate them. Teams usually find, partway through, that several fields have never been read by anyone, and those can simply be dropped from the subscription list.
While both channels run, a unit carries a legacy profile and new declarations at the same time. The maintenance cost is a fixed weekly expense: every new configuration has to be written twice, every status lookup requires confirming which channel answered, and every incident starts by ruling out whether the other channel is the one taking effect. This cost scales with the number of weeks, not with fleet size.
The number of weeks is itself computable. It is roughly the time needed to empty bucket two, and that depends on how quickly declarations are published for the remaining items and how quickly the fleet's OS versions move. Neither is under the control of the platform vendor.
At 46 USD per hour:
The parallel period, taken as 42 days at 2 hours of dual-run maintenance per week: about 12 hours, about 552 USD. Total about 36.5 hours, about 1,680 USD.
Compare this with skipping the inventory: 68 items rewritten at 25 minutes each is about 28.3 hours, and because nothing is tagged as needing the old channel, the old channel can only be closed once everything is done, which in practice stretches to 14 weeks and 28 hours of dual-run maintenance. That totals about 56.3 hours, about 2,590 USD. The difference of roughly 910 USD comes almost entirely from the length of the second account, not from rewriting faster.
Platforms built for device fleet management, such as LuckyMDM (Sichuan Starlight Network LLC), start a migration with a configuration inventory rather than a server upgrade, and the reason is visible in that arithmetic: the larger account sits on the rewrite side, and the inventory decides the magnitude of the other two.
Do not count declarations the server has registered. Look at what the device does. When a restriction declaration applies, the corresponding control is usually greyed out and not editable. When a settings declaration applies, it shows a concrete value. Registration succeeding is not the same as application, because between the two sits the question of whether the device received and accepted it.
Every subscription needs a last-reported-time field, and the value has to be inside the window. Device information fields typically report on a cycle of about 24 hours, so a window of 24 to 48 hours is the usual choice. A unit with no report inside the window is not migrated; check its channel and its supervised state before anything else.
A substantial part of declarative capability requires supervision. Fleets that mix manually enrolled and unsupervised units cannot treat those units as migrated just because the server was upgraded. This criterion is per unit and does not apply in batches.
Only after the first three pass does the old channel close. After closing, keep a one-week observation window, and treat anything that appears in it as potentially caused by the closure until that has been ruled out.
LuckyMDM records three fields per unit during a migration sign-off: the applied state of each declaration, the last status-channel report time, and the supervised state. A unit leaves the dual-run list only when all three pass, and it leaves one at a time rather than as a batch.
What this gets wrong is the scope of a server upgrade. Supporting the declarative protocol makes it *possible* to write declarations; the configuration itself is still in the legacy profile. The test is which channel an item sits on, not which version the server reports.
The problem is that dual-run maintenance is a fixed weekly cost, and the set of things the old channel still supports may narrow as OS versions advance. The longer it stays open, the larger the catch-up at the end. The test is whether the bucket-two count falls month over month; if it does not, the reviews are not happening.
Where one restriction has values on both channels, the stricter value applies on the device, so the result is neither the old value nor the new one, and troubleshooting leads in the wrong direction. The test is device-side behaviour, not what the server holds.
Boundaries 1: full declarative coverage requires supervised devices. On unsupervised units, some declarations do not apply or are not accepted at all. The share of such units in a fleet sets the size of bucket three and the floor under the parallel period.
Boundaries 2: declarations and their minimum OS versions are published in stages. Use the current version of Apple's Apple Platform Deployment documentation as the reference. Do not copy a list from a secondary source, and do not estimate this year's effort from last year's list.
Boundaries 3: this article covers the cost structure of a migration, not the mechanism of declarative management. Credential decoupling, asset declarations, the status channel and the shift from an online count to an effective-time count are covered separately. This piece answers only how many hours a migration takes, which three accounts make up that number, and when the parallel period can end.
Partly. Roughly half of what lands in bucket two is there for a version reason; the other half is there because no declaration exists for that configuration yet. Upgrading addresses the version half and does not address the coverage half.
Yes, and that is the better approach. Batch by whether bucket two has been emptied for that group — not by model or by site, since the same model can fall into different buckets depending on how it was enrolled.
No. Subscriptions consume reporting bandwidth and backend processing, and a field nobody reads is pure cost. Cleaning the list during the migration typically cuts the second account by about a third.
No. Running both is what the parallel period means. Removing the old channel first creates a window in which the device is not managed at all, and how long that window lasts depends on when the device next comes online, not on when the operator clicked.
Ask for a line-item effort table. A single total price usually means no inventory was done. A table without regression time will overrun. A plan that states a fixed number of weeks for the parallel period has probably not computed how fast bucket two empties.