You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Charging: Record the tanzanite session re-verification in the ledger (#71)
The 2026-08-14 beta run on the Redmi Note 14 (tanzanite) failed its
temporary-session check: the native-change observer cancelled on a
notification carrying no value change, so the device charged to 100%
with Battery protection never restored. That was fixed by
NativeChangeGuard (#65), which shipped in v0.3.4-beta0.
The contributor re-ran the session test on that build (issue #48): the
session reached 100% and Battery protection was re-written automatically
with the cable still connected. RESTORE_FULL therefore fires without a
disconnect, and the guard holds against the exact notification pattern
that produced the bug.
Ledger row and known-gaps bullet updated; the remaining unrun items on
this device are the disconnect-early restore, the Shizuku tier, boot
recovery, and an R8 smoke.
Copy file name to clipboardExpand all lines: .claude/skills/device-qualification/SKILL.md
+11-3Lines changed: 11 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,7 +51,7 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
51
51
| Xiaomi | Xiaomi 13T `2306EPN60G` HyperOS 2.0 (`ro.mi.os.version.code=2`) |**Partial** — mapping/readback/session verified; the adaptive 80% hold could not be triggered, so daemon-level hardware enforcement is **not yet demonstrated**| Read matrix, both-direction writes, session at 100%, unknown-value refusal, R8 beta | 2026-07-21 |
52
52
| OnePlus (Oplus) | OnePlus Nord CE4 Lite `CPH2621` ColorOS 15 (`ro.build.version.oplusrom=V15.0.0`) | Full — enforcement directly observable (device holds at 80%); external writes stick | Two mutually-exclusive `system` keys (Charging limit / Smart charging), WSS-only write rejected + Shizuku write succeeds for all three policies, WSS-only UX (controls disabled + Shizuku-required banner) | 2026-07-21 |
53
53
| GrapheneOS | Pixel 9 Pro XL `komodo`, GrapheneOS 2026080501 / Android 17 — **REMOTE qualification via issue #49** (tester-run protocol, not maintainer hardware) |**Enforcement observed**: held at 80% with shield, `dumpsys battery` status=4/Charging state=4/policy=2 (limit on) vs 2/1/1 (off); shell-UID writes move the Settings UI live, **latch at plug-session start** — mid-session writes have no hardware effect until unplug→replug, replug reliably applies the current value | Key isolation (`settings list` diff → single `global battery_charge_limit` 0/1), write→UI both directions, mid-session no-op both directions, replug latch both directions, hardware signal both states. **NOT run**: app-context access tiers (WSS write from Amply, `app.grapheneos.*` package visibility), sessions/boot recovery, wireless, factory-absent key state, secondary user | 2026-08-12 |
54
-
| Xiaomi (HyperOS 3) | Redmi Note 14 `24117RN76G` (`tanzanite`), HyperOS 3.0.302 / Android 16 (`ro.mi.os.version.code=3`) — **REMOTE qualification via issue #48** (contributor-run protocol, not maintainer hardware) | **Both-direction enforcement of EXTERNAL shell-UID writes observed** (the same write path as Amply's Shizuku service): `settings put … 2` below the cap → Battery protection active, Settings UI follows immediately, held at 80% for ~20 min under active use (voltage 4228 mV holding vs 4391 mV charging, charge counter 4283 vs 4341 corroborate; sysfs `current_now` permission-denied, so no current reading); `settings put … 0` mid-hold → charging resumes past 80 immediately. **No hardware hold signal**: `dumpsys battery` reports `status: 2` / `Charging state: 0` / `Charging policy: 0` in both states → read-back-only verification | Key mapping (three modes incl. `2` = Battery protection @80, cap fixed — no percent picker), external write → UI both directions, sustained hold, mid-hold release. Beta run 2026-08-14 added: app-context three-mode control (direct WSS), factory-absent key = Intelligent (confirmed). Session restore FAILED in that run (observer noise cancel — app bug, fixed; see Known gaps). **NOT run**: Shizuku tier, boot recovery, wireless, R8, session re-verify post-fix | 2026-08-14 |
54
+
| Xiaomi (HyperOS 3) | Redmi Note 14 `24117RN76G` (`tanzanite`), HyperOS 3.0.302 / Android 16 (`ro.mi.os.version.code=3`) — **REMOTE qualification via issue #48** (contributor-run protocol, not maintainer hardware) | **Both-direction enforcement of EXTERNAL shell-UID writes observed** (the same write path as Amply's Shizuku service): `settings put … 2` below the cap → Battery protection active, Settings UI follows immediately, held at 80% for ~20 min under active use (voltage 4228 mV holding vs 4391 mV charging, charge counter 4283 vs 4341 corroborate; sysfs `current_now` permission-denied, so no current reading); `settings put … 0` mid-hold → charging resumes past 80 immediately. **No hardware hold signal**: `dumpsys battery` reports `status: 2` / `Charging state: 0` / `Charging policy: 0` in both states → read-back-only verification | Key mapping (three modes incl. `2` = Battery protection @80, cap fixed — no percent picker), external write → UI both directions, sustained hold, mid-hold release. Beta run 2026-08-14 added: app-context three-mode control (direct WSS), factory-absent key = Intelligent (confirmed). Session restore FAILED in that run (observer noise cancel — app bug, fixed in #65). Re-verified 2026-08-16 on v0.3.4-beta0: **session restore PASSES** — full-charge session ran to 100% and Battery protection was re-written automatically while still plugged. **NOT run**: Shizuku tier, boot recovery, wireless, R8, unplug-early restore | 2026-08-16 |
55
55
| GrapheneOS (follow-up) | Same device, **0.3.2-beta0 on-device report via issue #49**|**Package detection VERIFIED from app context** (`is_grapheneos=true` with the FLAG_SYSTEM check); **unprivileged key read DENIED** — `has_battery_charge_limit=false` while the very same report showed `battery_charging_status=4` (limit enforcing). Root cause in GrapheneOS source: the key is `@Protected(read = SYSTEM_UI, readWrite = SETTINGS)` (frameworks_base `c30c6393`); SettingsProvider throws SecurityException for all other packages **including WSS holders**, with the shell UID explicitly exempt ("ADB is used for testing", `e87c93a2`) — so the tester's earlier adb runs ARE the Shizuku-path evidence. Factory-absent semantics resolved from source: `BoolSetting(..., default false)` → absent = off | Detection + fail-closed probe verified live; adapter re-gated to Shizuku-only in response | 2026-08-13 |
56
56
57
57
## Known gaps
@@ -234,8 +234,16 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
234
234
re-written. Root-caused from the run's artifacts — the contributor's 22:22 screenshot shows the
235
235
dashboard already idle at 96% while their 2s key monitor shows no value change after the 21:59:08
236
236
override write. Fixed by `NativeChangeGuard` (readback-verified cancellation, see
237
-
`rules/architecture.md`); **session-lifecycle re-verification is the headline ask for the next beta**.
238
-
Boot recovery and R8 smoke remain NOT RUN.
237
+
`rules/architecture.md`).
238
+
-**Session restore RE-VERIFIED PASS (2026-08-16, issue #48, v0.3.4-beta0 — the first release carrying
239
+
the #65 fix).** With Battery protection active, the contributor started the temporary full-charge
240
+
session; the battery charged past the cap to 100% and Amply re-wrote Battery protection automatically
241
+
on reaching full, **with the cable still connected**. So the `RESTORE_FULL` path fires without needing
242
+
a disconnect, and `NativeChangeGuard` no longer eats the session on this device's notification
243
+
pattern — the guard fix is now confirmed against the exact device that produced the bug. This closes
244
+
the only failing item from the 2026-08-14 run; `tanzanite` becomes the second remote-qualified device
245
+
with a verified session lifecycle. **Still NOT run** here: the disconnect-early restore path
246
+
(`RESTORE_DISCONNECTED`), the Shizuku access tier, boot recovery, and an R8 beta/release smoke.
239
247
-**Cap is fixed at 80%** (no percent picker in the Battery protection screen), and mode `2`'s own
240
248
description says the device will "charge fully only when scheduled" — HyperOS reserves an OEM-side
241
249
scheduled full charge while in Battery protection. No code impact (verification is settings readback;
0 commit comments