Version 5.2.0b4 (19-Oct-2026)
- Fixed:
{minutesBeforeDeadline}inPreDeadlineThresholdMessageshowed the crossed threshold (for example,45) instead of the time actually left, so a late run (padded deadline fromsetPastDuePaddedEnforcementDate, heartbeat delay, wake from sleep, collapsed stale thresholds, or a manual run) overstated time remaining (Issue #160)- The placeholder now shows minutes remaining until the effective deadline, rounded up and never above the crossed threshold, computed when the dialog copy is applied. On-time runs still show the threshold value.
Pre-deadline threshold reminder due: …andApplying …-minute pre-deadline threshold dialog copynotices now append(<n> minute(s) remaining).- Threshold delivered/skipped state, scheduling, and preference keys are unchanged. Known: copy reads
1 minuteswhen one minute remains.
Resources/diagnoseReminderState.zshchecklist version now5.2.0b4; no diagnosis logic changes.
Version 5.2.0b3 (18-Oct-2026)
- New low-battery warning: when a Mac is running on its internal battery below
MinimumBatteryPercentage, the reminder dialog asks the user to connect power and leave it plugged in until the macOS update completes (Issue #157; thanks, ebbg!)- New
MinimumBatteryPercentagepreference: integer0–100, script andResources/sample.plistdefault50;0disables. A non-integer or out-of-range value logs[WARNING]and falls back to50. - New
BatteryWarningMessagestring (plusBatteryWarningMessageLocalized_<code>foren,de,fr,es,pt,ja,nl, andit) with the{batteryPercentage}placeholder, inserted through the new{batteryWarningMessage}placeholder. {batteryWarningMessage}added after{diskSpaceWarningMessage}inMessage,AggressiveModeMessage, andPreDeadlineThresholdMessage, and appended toPastDeadlinePromptMessageandPastDeadlineForceMessage(all localized variants included). Restart mode still suppresses the uptime and disk-space warnings but keeps the battery warning, because a restart does not charge the battery.- Detection uses positive evidence only from
pmset -g ps: the warning appears only when the header readsNow drawing from 'Battery Power', a charge parses from the-InternalBattery-line, and that charge is below the threshold. There is no warning on AC power (includingAC attached; not charging), on UPS power, on Macs without an internal battery (desktops, VMs), or when the charge can't be read (logged at[WARNING]). The charge never comes from a UPS line. - Each run logs one
[NOTICE] Battery: power source <source>; internal battery <charge>|not present|unreadable; MinimumBatteryPercentage threshold <n>%; warning shown|suppressed|disabledline. - Messaging only:
PastDeadlineRestartBehaviorPrompt/Forceeligibility, timing, and suppression are unchanged. - Applied the same change to
Resources/reminderDialogPreferenceTest.zshandResources/createPlist.zsh. The preference test helper adds--battery-percentage <0-100>to preview the warning on a desktop or AC-powered Mac; productiondor.zshnever simulates power state. - When
assemble.zshimports a prior.plist/.mobileconfig, it prints anℹ️line listing each imported template that lacks{batteryWarningMessage}(and adds it to Recommended review items); it never rewrites admin-authored copy. - The non-English
BatteryWarningMessageLocalized_<code>strings are new and would benefit from native-speaker review.
- New
Resources/diagnoseReminderState.zsh(1.4.0) knowsMinimumBatteryPercentage(flagged when outside0–100) andBatteryWarningMessage, so neither is reported as an unknown key, and it now lists the runtimeBattery:andDisk space:lines under decision-related client-log lines.- Fixed: when the Finder-aligned disk-space query failed, the fallback
[WARNING](and any[ERROR]for unreadable disk data) was attempted before the logging helpers were defined, so it was never written to the client-side log. Both are now logged right after theInitiating …preamble. - Upgrade note: Deploy the updated
dor.zshbefore, or together with, any Configuration Profile that contains{batteryWarningMessage}; older scripts replace only the placeholders they know, so the dialog would show the literal text{batteryWarningMessage}. CustomizedMessage,AggressiveModeMessage,PreDeadlineThresholdMessage,PastDeadlinePromptMessage, andPastDeadlineForceMessagestrings (and theirLocalized_<code>variants) need{batteryWarningMessage}added by hand; without it, the warning never appears.
Version 5.2.0b2 (16-Oct-2026)
- Client-side log entries now speak to Mac Admins: every run that shows no reminder ends with what happened, why no dialog appeared, and what to do next (Issue #154)
Normal DDM resolver result: status=…is nowDDM update check: <plain-language outcome> (status=…; source=…; reason=…);status=,source=, andreason=tokens are unchanged for support and tooling.Resolver suppression summary: …is replaced byNo reminder shown: …followed byNext step: …, logged at the end of the run just beforeExiting ….missingand up-to-date outcomes log at[NOTICE];conflict,noMatch,invalidVersion, and unrecognized states keep[WARNING].- Before:
Normal DDM resolver result: status=missing; source=none; reason=No DDM declaration candidates found in install.log…MDM fallback requirement is not configured; preserving normal DDM resolver status 'missing'.After:DDM update check: no DDM-enforced macOS update found (status=missing; …),DDM Emergency Fallback not configured (dor-fallback-declaration.plist absent); nothing to fall back to,No reminder shown: this Mac has no DDM-enforced macOS update pending (…), andNext step: If an update should be enforced, confirm this Mac is in scope of a DDM software-update declaration in your MDM, then search '/var/log/install.log' for 'EnforcedInstallDate'. - Each unresolved state names its own next step (scope to one declaration, correct the target version, confirm the version is offered in Software Update, or configure a DDM Emergency Fallback).
reason=text drops internal terms such as "declaration candidates", andResolver context:lines are nowinstall.log context:. conflictnow says which kind: two different declarations, an available update that does not match the declared version, orsoftwareupdated found no update to enforce, yet DDM-enforced macOS <version> is still declared(previouslyDeclaration persisted after 'No updates found for DDM to enforce'), each with a matching next step. When installed macOS already meets the declared version and the conflict is eithersoftwareupdatedfinding nothing to enforce orsoftwareupdatedoffering a different (for example, newer major) update, the outcome is[NOTICE] No reminder shown: macOS <installed> already meets DDM-enforced macOS <version>; …with aNext step: None; …; suppression andstatus=conflictare unchanged. Two different declarations always stay[WARNING].- Up-to-date Macs now log
No reminder shown: macOS <installed> already meets DDM-enforced macOS <version>withNext step: None; this Mac is up to date. - Display-window, periodic, Update Tonight, quiet-period, and presentation exits now log
[QUIT] No reminder shown: …naming the controlling preference (DaysBeforeDeadlineDisplayReminder,OutsideDisplayWindowPeriodicReminderDays,QuietPeriodMinutes,MeetingDelay) plus when the reminder returns. Missing swiftDialog, non-root, no console user, and overlapping-run exits name a next step in the same[FATAL ERROR]line. - Demo mode now logs that it ignores DDM state and always shows a sample reminder, even on an up-to-date Mac.
- Other everyday lines are shorter and plainer:
Installed macOS Version: …andInstalled macOS Build: …are combined intoCurrently installed macOS <version> (<build>).Current Logged-in User: …andCurrent Logged-in User First Name (ID): …are combined intoCurrent Logged-in User: <user>, <first name>, <UID>; while waiting for a console user,Logged-in User Counter:names the current console user.- The three
Resolved DDM declaration source/version/enforcement datelines and the rawResolved declaration context: …install.log line (declaration key, details URL, and all) are replaced by[INFO] DDM declaration logged by softwareupdated at <timestamp> (<source>); version and declared deadline are on theDDM update check:line. install.log context:lines drop the computer name and process ID.Software Update handoff: requirementSource=…; normalResolverStatus=…; target=…; deadline=…; action=…is nowOpening Software Update for <user> to install macOS <version> (deadline <effective deadline>; source=…; status=…).Resolved DailyReminderTimes,Resolved MinutesBeforeDeadlineReminderSchedule, and the dialog-language decision now log at[PRE-FLIGHT]; the language decision is logged once per run unless it changes.
- Runtime and deployment logs say "DDM Emergency Fallback" (matching the documentation) instead of "MDM fallback requirement". Rejected Parameters 5 and 6 log a
Next step:with the expected format. - No changes to reminder timing, DDM trust rules, suppression decisions, preferences, or log format
<scriptName> (<version>): <timestamp> - [<level>] <message>.
Resources/diagnoseReminderState.zsh(1.3.0) reads bothDDM update check:and earlierNormal DDM resolver result:lines, bothCurrently installed macOSand earlierInstalled macOS Version/Buildlines, and both logged-in user formats (for redaction), so logs from older clients still diagnose- The Client-log runs outcome column now shows the run's
No reminder shown:line or specific[QUIT]reason instead of the closingTicking away …line every run ends with. - A generic
Macinstall.log computer name is no longer redacted, so plain-language lines such as "this Mac" stay readable.
- The Client-log runs outcome column now shows the run's
MinimumDiskFreeGBsample default (Resources/sample.plist, generated.plist/.mobileconfig) raised from26to35after a fully staged macOS update was observed atUpdate Size: 26.60 GB. The script default (999),0-disables behavior, and imported values from a prior.plistare unchanged.- Upgrade note: Scripts, Extension Attributes, or log searches keyed on
Normal DDM resolver result,Resolver suppression summary,MDM fallback requirement,Version Comparison Result: No DDM enforcement log entry found,Installed macOS Version,Resolved DDM declaration, orSoftware Update handoffmust be updated; key onDDM update check:,status=,No reminder shown:,Currently installed macOS,Opening Software Update, andDDM Emergency Fallbackinstead.
Version 5.2.0b1 (16-Oct-2026)
assemble.zshnow accepts a prior.mobileconfigas well as a prior.plistfor prior-configuration import, so a hand-edited Configuration Profile can be re-assembled without the generated.plistdrifting out of sync (Issue #153; thanks, @anewhouse!)- Works from the command line (
zsh assemble.zsh /path/to/prior.mobileconfig) and the interactive drag-and-drop prompt; a.mobileconfigargument is no longer mistaken for an RDNN. - CMS-signed profiles are decoded with
security cms -Dbefore reading. No new dependencies: extraction usesPlistBuddy -x, so dotted preference domains work on macOS 14 and earlier. PayloadContentis walked rather than assuming index0. Supported layouts: acom.apple.ManagedClient.preferencespayload with a*.dormdomain (Forcedpreferred;Set-OnceorOftenused with a⚠️notice only whenForcedis absent), or a payload whosePayloadTypeis the*.dormdomain (for example, iMazing Profile Editor), withPayload*metadata keys stripped.- Exactly one DDM OS Reminder domain is required. Missing, multiple, or ambiguous payloads fail with a clear
❌message (for example,❌ Prior mobileconfig contains multiple DDM OS Reminder preferences payloads (…)), as do multipleForcedentries and files that are neither a profile nor a CMS envelope. - RDNN and deployment-lane inference use the original filename. New lane suffixes:
-<lane>.mobileconfig,-<lane>-unsigned.mobileconfig, and-<lane>-signed.mobileconfig; near-miss filenames such as…-prod-5.1.0.mobileconfigprint the existing warning. - Version metadata comes from the profile's
PayloadDescription(Configures DDM OS Reminder (<version>) …), falling back toPayloadDisplayName(DDM OS Reminder (<version>), as written byResources/createPlist.zsh); profiles built elsewhere import with the existing best-effortmissing version metadatawarning. - RDNN is still inferred from the imported
ScriptLog; a⚠️warning now flags a profile whose*.dormdomain disagrees with that RDNN (for example, acreatePlist.zshprofile whoseScriptLogstill uses the placeholder RDNN). - Extracted settings live in a
mktemp -ddirectory under$TMPDIR(neverArtifacts/), removed by anEXITtrap, including on Ctrl-C. - Supplying more than one prior
.plistor.mobileconfigpositional argument now fails with❌ Only one prior .plist or .mobileconfig may be supplied …instead of treating the extra path as an RDNN. - A prior
.plistthat is actually a Configuration Profile now adds a hint to rename it with a.mobileconfigextension. Otherwise, prior.plistimport, messages, lane and RDNN inference, and generated output are unchanged.
- Works from the command line (