Hardening pass on device enrollment, QR provisioning alignment, kiosk, and command delivery — driven by an end-to-end test of the enrollment flow against openmdm-demo on an emulator.
Enrollment protocol & security
- Enum-valid enrollment
method. The agent reportedmethodasdevice_code:<CODE>, which is not one of the server'sEnrollmentMethodvalues — any server configured withenrollment.allowedMethodswould reject every enrollment. Manual enrollment now sendsmanual, managed provisioning sendsqr; the device code stays the on-device pairing reference. (6381683) - HMAC fallback signs with the provisioned secret. The QR admin-extras
openmdm.device_secretwas persisted but ignored — the HMAC path always signed with the compiled-inBuildConfig.DEVICE_SECRET, so a stock/provisioned APK failed enrollment against any server on the HMAC path. The secret now follows the same precedence as the server URL: provisioned wins, compiled-in is the fallback. (0a5c692) - Protocol-v2 envelope decoding. The agent stamps
X-Openmdm-Protocol: 2on every request but never decoded the{ok, action, data}envelope — Gson parsed the envelope against the flat payload models, every field landed null, and enrollment crashed with an NPE ondeviceId. NewAgentEnvelopeInterceptorunwraps success todataand maps failure actions to legacy statuses (reauth→401,unenroll→404,retry→503) with the action in anX-Openmdm-Actionheader; non-envelope bodies pass through untouched. (ebb11f0) - App versionName length. A real-world Android
versionNamelonger than the server'svarchar(50)(Google's TTS app ships 53 chars) made Postgres reject the insert and the server 500 the entire heartbeat, on every heartbeat. The agent now truncates defensively; the column was also widened server-side in@openmdm/drizzle-adapter@0.6.3. (688b870)
QR provisioning & enrollment UX
- QR aligned to managed provisioning only. An in-app QR scanner was prototyped and then deliberately removed: the QR is the Android managed-provisioning QR, scanned by the setup wizard on a factory-reset device — the only path that grants Device Owner. An in-app scan enrolls without DO (no silent installs, no true kiosk), which reads as broken, so the enrollment screen offers device-code entry only. (
f185670, reverted in9d0fc5d) - QR parser reads flat payloads.
openmdm.enrollment_tokenandopenmdm.config_urlwere only read from the admin-extras bundle, unlike every other key — flat JSON payloads silently dropped the pairing token. (170ecc9) - Provisioning progress state. During managed-provisioning enrollment the screen now shows "Applying provisioned configuration — enrolling…" instead of a device-code prompt that would race the background worker.
Kiosk / Device Owner
- Lock-task dedup.
DeviceManager.startLockTaskModepassed[packageName, ownPackage]tosetLockTaskPackages, which rejects a duplicate with "duplicate element" — so a single-app kiosk whose target is the agent failed. Both lock-task entry points now dedup. (c9b85db)
Command delivery & reliability
- Fast in-process command poll loop. With no push channel (polling provider), commands were only fetched on the WorkManager heartbeat, whose periodic floor is 15 minutes — so a sub-minute policy interval was silently clamped and commands sat undelivered for up to ~15 min.
MDMServicenow runs an in-process poll loop while the foreground service is alive, gated to the polling provider (persisted from the enrollment response'spushConfig) and a no-op for fcm/mqtt/websocket. Measured drain dropped from ~7 minutes to ~16 seconds. (9952511, gate extracted + unit-tested in2e9ca49)
Verification
Verified end-to-end on an emulator: device enrolled as Device Owner (dpm set-device-owner), commands drain autonomously in ~16s, kiosk enterKiosk completes success=true, and pushConfig persistence + the poll-loop provider gate are covered by unit tests. Coordinated server-side fixes shipped in @openmdm/core@0.11.1 (identity errors map to real HTTP statuses instead of 500) and @openmdm/drizzle-adapter@0.6.3 (app-version columns widened).
Full commit range: f9796f6..2e9ca49