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: Scope the GrapheneOS replug-latch claim to its qualifying device
The ledger's Pixel 9 Pro XL row stated that an externally written
battery_charge_limit value is picked up reliably at the next plug session,
phrased as a property of the ROM rather than of that device. A Pixel 10 on a
later GrapheneOS build refutes it: a write made while unplugged, and confirmed
off in the ROM's own Settings UI, was still ignored at the next plug-in, with
the device holding at exactly 80%. The same device's native Settings toggle
works normally, so the divergence is the external-write channel, not the cap.
Scopes the claim to the device and build it was proven on, and records the
refutation as a known gap: what was observed, why the cause is undetermined
(the two devices were never tested on each other's builds, so a ROM release
change and a Pixel 10 generation difference fit equally), the blast radius
across every write path on this adapter, and the untested protective
direction, which is the open safety question and a hold on widening anything
on this ROM.
Evidence: issue #49, comment 5414184320.
Copy file name to clipboardExpand all lines: .claude/skills/device-qualification/SKILL.md
+26-2Lines changed: 26 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,7 +67,7 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
67
67
| Samsung | Galaxy Tab A9+ SM-X210 One UI 8.0; Galaxy S20 FE SM-G781B One UI 4.1 | Full — sync readback + HAL enforcement | Modern multi-mode + legacy toggle, session E2E, native-change cancel, reboot recovery, R8 beta | 2026-07-21 |
68
68
| Xiaomi | Xiaomi 13T `2306EPN60G` (`aristotle`) HyperOS 2.0 (`ro.mi.os.version.code=2`); 2026-08-16 re-run on `OS2.0.216.0.VMFEUXM` / Android 15 | **Partial, now characterized** — external shell-UID writes provably drive the daemon identically to native UI taps (`getProtectMode` → `checkUiModeProtect` → `setEnable`, ~80 ms, both directions, no hidden UI-only flag). The adaptive 80% hold is still **unobserved**: charged 59→100% with no plateau, `getNightChargingState` = 0 on all 140 evaluations. Cause identified — the gate is a *learned* charging-routine model (`key_ave_night_charge_start_minutes`, `…_sd`, `key_enter_night_charge_times`), not a clock window (forced clock to 02:30 did not open it). **BLOCKED on an unused test device, not FAILED.** No hardware hold signal (`Charging state`/`policy` = 0/0) | Read matrix, both-direction writes, session at 100%, unknown-value refusal, R8 beta (2026-07-21). Added 2026-08-16: external-vs-UI write-path equivalence, native UI reflects external key, learned-schedule root cause, clock-forcing negative | 2026-07-21 / 2026-08-16 |
69
69
| 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 |
70
-
| 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 |
70
+
| 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, and on this device/build the replug applied the written value in both directions. **The replug half does not generalize**: refuted on `frankel` (Pixel 10, 2026081301) — see the known-gaps entry | 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 |
71
71
| 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 |
72
72
| 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 |
73
73
| GrapheneOS (2nd device) | Pixel 10 `frankel`, GrapheneOS 2026081301 / Android 17, **Amply 0.5.0-beta0 (foss/beta, R8-minified) on-device report via issue #49** | **No new hardware observation** — the reporter states the app works with Shizuku granted, but reported neither an observed 80% hold nor a session run, so this row adds no enforcement evidence. The `komodo` enforcement observation above remains the only one | **Amply's own Shizuku path verified on hardware for the first time**: with Shizuku set up the app reads the key and offers the control, where the 0.3.2-beta0 run without Shizuku showed "setting not present". Second device, and the first on a Pixel generation other than the 9 Pro XL all prior work used. R8 smoke clean (the `beta` build type minifies and shrinks resources), and `ReconnectSupport.NONE` renders correctly as an unavailable gesture rather than a dead control. **NOT run**: observed 80% hold through the app, full-charge session + restore, boot recovery, wireless, secondary user | 2026-08-23 |
@@ -180,7 +180,31 @@ only after adding a row here. Detailed run narratives live in each adapter's lan
180
180
2nd-device ledger row) shows Amply's own Shizuku service reading the key and offering the control on real
181
181
hardware, under R8, so those two are closed. What that report does *not* cover, because the reporter did not
182
182
run them: an 80% hold observed through the app rather than over adb, a full-charge session and its restore,
183
-
and boot recovery. Every enforcement claim on this ROM still rests on the `komodo` adb runs.
183
+
and boot recovery. Every enforcement claim on this ROM still rests on the `komodo` adb runs, and one of them
184
+
(the replug latch) is refuted on `frankel` — see the next entry.
185
+
-**External writes are not honoured on every build — REFUTED on `frankel`** (Pixel 10, GrapheneOS 2026081301 /
186
+
Android 17; issue #49, 2026-08-25). With Amply out of the loop entirely, a bare
187
+
`settings put global battery_charge_limit 0` made while unplugged, and confirmed off in the ROM's own Settings
188
+
UI, was still ignored at the next plug-in: the device held at exactly 80% (level climbed 74→80, then
189
+
NOT_CHARGING at 80 with power connected, no level above 80 all day, thermals ~33 °C so not a throttle; the
190
+
app's package appears nowhere in the 52k-line capture). The same device's **native Settings toggle works
191
+
normally**, so the divergence is the external-write channel, not the cap. Cause **undetermined**: `komodo` was
192
+
qualified on 2026080501 and `frankel` observed on 2026081301, neither device tested on the other's build, so a
193
+
ROM release change and a Pixel 10 generation difference fit the evidence equally. No public upstream report
194
+
matches this direction (the known ones are the inverse — charging past the cap while the limit is on). One
195
+
caveat on the evidence: the capture carries no settings-provider logging, so "the key read 0 at plug time"
196
+
rests on the reporter's statement plus the ROM's UI, not on instrumentation.
197
+
-**Blast radius**: every write on this adapter shares one path, so on a device behaving like this the
198
+
dashboard/widget/tile persistent writes, session restore, and boot recovery are all hardware-level no-ops
199
+
that read back as verified. Boot recovery's convergence loop returns DONE on the first tick for the same
200
+
reason (a matching settings readback is treated as convergence). Nothing in the app can notice: this adapter
201
+
does not set `enforcementEvidenceRequired`, and the passive verdict engine only detects a climb *past* a
202
+
cap, which is the opposite direction from a ROM enforcing a cap that is configured off.
203
+
-**The protective direction is UNTESTED, and it is the open safety question.** Both observed failures wrote
204
+
the limit *off* while the ROM kept enforcing, which leaves the battery protected and merely makes the UI
205
+
dishonest. Whether a write turning the limit *on* is equally ignored — which would mean Amply claims
206
+
protection that is not enforced — has never been run by anyone. Asked of the reporter 2026-08-25. Do not
207
+
widen, re-qualify, or relax anything on this ROM until it is answered.
184
208
-**State 4 below the limit unverified** — evidence was sampled at the 80% hold; if the ROM reports 4 only while
185
209
holding, a FixedLimit pending clears late (at the hold) instead of instantly. Cosmetic.
186
210
-**A plugged restore configures but cannot enforce** — restore-at-100%, the 24h safety timeout,
0 commit comments