# M1 Android verification report Implementation commit `58dd3021c219ea8fe17747a04859f4c66bd7b464` on `chore/android-verify`, based on `origin/master` `0b9f605a40fe8bf1a6ced14c0114f94e9c9309d7`. All seven committed files match the frozen tested bytes. `commit-evidence-map.json` maps the independent review and runtime evidence to this commit. No source files changed. Original native issue QA ran at `edc7c12`, whose `src/` tree is byte-identical to this commit. Engineers can build, deploy and verify PodNotes in real Obsidian Android with seven npm commands. The wrapper owns the emulator and setup services across tool calls, keeps a worktree-specific vault, verifies the loaded mobile player, reports real download completion and native memory, captures screenshots and logs, and stops without deleting the AVD or vaults. The recipe is `.claude/skills/verify/features/mobile.md`. Runtime used Obsidian Android 1.14.4, PodNotes 2.21.1, Android 14 API34 x86_64 Pixel6, WebView113.0.5672.136 and emulator37.2.12. The hashed vault is `podnotes-3a224ec31393-omd-scratch`. Initial native QA used `podnotes-android-verify-debug`. This is Android phone evidence. iOS and tablet behavior remain unverified. ## Issue verdicts Each original Android phone check passed. Exactly one evidence comment was posted per issue. None was reopened. | Issue | Observed result | Evidence comment | Screenshot | | --- | --- | --- | --- | | #100 | Native local-file selection played `Media/Local talk #1 (final).mp3` and audio time advanced. | [Comment](https://github.com/chhoumann/PodNotes/issues/100#issuecomment-6105823837) | [Image](https://files.bagerbach.com/100-playing-y8ss4pd06v52.png) | | #110 | Three cold app starts kept PodNotes enabled with no dormant player leaf or audio element. | [Comment](https://github.com/chhoumann/PodNotes/issues/110#issuecomment-6105823950) | [Image](https://files.bagerbach.com/110-restart-3-xng0v9ontvcf.png) | | #113 | A real 249,955,445-byte episode downloaded without an app or renderer crash. | [Comment](https://github.com/chhoumann/PodNotes/issues/113#issuecomment-6105824122) | [Image](https://files.bagerbach.com/113-downloading-siq7t6wj3cc1.png) | | #89 | A native mobile-toolbar tap wrote the expected timestamp link into Markdown. | [Comment](https://github.com/chhoumann/PodNotes/issues/89#issuecomment-6105824251) | [Image](https://files.bagerbach.com/89-toolbar-final-dmp2l1chkxyj.png) | | #109 | A native Add tap saved the Lex Fridman feed and changed Add to Remove. Phone layout only. | [Comment](https://github.com/chhoumann/PodNotes/issues/109#issuecomment-6105824406) | [Image](https://files.bagerbach.com/109-added-9d8jwaw9jp3o.png) | [Original QA JSON](https://files.bagerbach.com/qa-evidence-3a5y5tnoaew0.json) records the feed, file, actions, versions and observed state. The #113 comment predates the later numeric process-start identity correction. The stronger final evidence below supports the Android crash verdict. ## Final runtime verification An independent Claude Opus5.5 verifier passed two complete stopped-state cycles on the frozen final code. Each ran `mobile:setup`, `mobile:verify`, `mobile:eval`, `mobile:screenshot`, `mobile:logs -- --seconds 2`, and `mobile:stop`. Existing `data.json` stayed byte-identical. Host and device `main.js` SHA-256 matched. The verified bundle hash is `c1dc3560ad689e2df0d055209fb4d2a428c2233a348deedb62b9d87a7b9406ed`. See `runtime-r4-report.md` and `final-r4-summary.txt`. The lead then ran two additional complete stopped-state cycles that included `mobile:download`, after the final audit identified that earlier download evidence preceded the last lifecycle fix. Both fresh fixture vaults matched `obsidian-e2e.config.mjs` `defaultData` exactly before feed selection. The real registered PodNotes URI handler selected Lex Fridman episode #501 DHH, and the download probe invoked the registered download command. All seven npm commands exited zero in each cycle. The feed is `https://lexfridman.com/feed/podcast/`. The enclosure is `https://media.blubrry.com/takeituneasy/ins.blubrry.com/takeituneasy/lex_ai_dhh_2.mp3`. | Final download run | Duration | Bytes | App PSS before / after, KiB | Renderer PSS before / after, KiB | | --- | --- | --- | --- | --- | | 1 | 128 s | 249,955,445 | 131,202 / 94,359 | 169,101 / 164,356 | | 2 | 133 s | 249,955,445 | 126,183 / 118,850 | 122,547 / 171,939 | Both runs retained the same numeric PID and start ticks for the app and renderer, left no partials, matched the final file stat to persisted registration, and answered a later CDP evaluation. Both device files matched the independently fetched host enclosure SHA-256 `534543418221b4d560994278b12cbb647f99aa697bde3c6e5cbf37116d98ab54`. See `final-r4-download-{1,2}.json` and their content proofs. Native memory uses `dumpsys meminfo --local`. Plain renderer meminfo caused a WebView native crash before a download during earlier instrumentation, so that callback was avoided. PSS values are before and after samples. They establish neither peak memory nor absence of a leak. Download times are observations, not a speed comparison. The original vault was restored with byte-identical 17,817-byte plugin data. Download fixture vaults remain separate. Both systemd units are inactive with MainPID0, both ownership records are absent, and no owned qemu or setup worker remains. The AVD is retained. The desktop isolated Obsidian dry-run reports no remaining processes. See `final-r4-restore-proof.json` and `final-handoff-cleanup.json`. ## Repository checks All checks exited zero on the frozen committed files. - `npm run lint`. - `npm run format:check`. - `npm run typecheck`. - `npm run build`. - `npm run test`. 1,296 tests passed across 84 files. - `npm run docs:build`. Exact docs requirements were installed in the scratch virtual environment and placed on PATH. - `npm run size`. Raw307,131 of330,000 bytes. Gzip97,161 of100,000 bytes. ShellCheck, Bash syntax, Node syntax, supplemental mobile-probe lint and `git diff --check` also passed. The standalone probe lint used `oxlint -A no-unused-expressions tests/mobile/*.js` because the download probe is an eval callback expression. See `final-checks.json` and its logs. ## Resources and deviations Guest allocation is4096MiB. An earlier observed qemu RSS was6,977,112KiB, about6.65GiB, including emulator overhead. This is an observed value, not a measured peak. The final two download snapshots were lower. - cache retained disk use is 9,271,480,320 bytes, 8.63 GiB. - avd retained disk use is 3,217,481,728 bytes, 3.00 GiB. - scratch retained disk use is 395,382,784 bytes, 0.37 GiB. - artifacts retained disk use is 80,687,104 bytes, 0.08 GiB. - nodeModules retained disk use is 676,532,224 bytes, 0.63 GiB. The shared adb server PID26847 was created by M1 during initial setup at04:53:47UTC. Earlier claims that it predated M1 were wrong and are corrected in the append-only trail. It remains intentionally running because the workflow forbids stopping the global adb server. It consumes about6.6MiB RSS. No emulator or setup worker is left running. Initial detached-launch lifetime was unstable. The final wrapper supervises qemu in its own systemd session and authenticates ownership with boot ID and process start ticks. A later orphan OMD setup worker was most likely responsible for a verifier guest shutdown, based on the saved timeline and logs. The shutdown command itself was not captured. The third fix places setup and its children in a separate owned systemd cgroup and stops that group before emulator cleanup. Saved interrupted-caller, wrong-token refusal, whole-group cleanup and normal cached-bootstrap proofs passed. Early failed runs remain recorded and do not count toward final acceptance. The final SDK installation path was not rerun from an empty cache, because the SDK and AVD were retained. Final cached bootstrap, interruption and normal lifecycle are proven. The case where an initially absent adb daemon starts inside the setup cgroup is reasoned from the implementation, not observed. The Device panel could not open this custom SDK AVD, so OMD and serial-specific adb drove the real app. Tablet resizing was abandoned after instability. No iOS attempt was made. No source, version, release or migration changes are included. Rebase was skipped under the standing order, and fetched `origin/master` was used instead of the stale local master. ## Follow-ups Immediate playback after a successful download reports media error4. Earlier inspection showed src `http://localhost/`; the final repeats showed an empty src and error4. Selecting the downloaded file later through native Local Files decoded it with readyState4 and audio time advancing from0.085 to13.773 seconds without an error. This is a separate product defect from the original #113 download crash. Its cause is not established. [Playback evidence](https://files.bagerbach.com/download-playback-q7xjx5u2rvrz.png). The player shows a1x label beside a1.50 playback-rate slider. A fifth saved podcast tile lacked artwork in the independent final smoke screenshots. These are separate UI follow-ups. The pinned OMD private launch and CDP adapters should be replaced by upstream lifecycle and socket-selection support when available. Additional GitHub issues and source fixes are outside M1 scope and are handed to the coordinator. ## Review and principles Claude Opus5.5 and GPT6Astra requested no final code changes. The Claude Opus5.5 final trail audit raised seven flags. Additional frozen-code downloads and seeding, commit-tree hashes, explicit adb ownership disclosure, corrected resource figures and appended task stops resolved the evidence and bookkeeping gaps. Fresh SDK, phone-only coverage, sampled-memory limits and the playback defect remain explicit limits. The audit compared recent transcript entries and saved artifacts rather than the entire session. Model the Domain led to durable session records. Build the Lever produced the seven rerunnable commands and probes. Separate Before Serializing Shared State led to hashed vaults and a real host lock. Prove It Works required real Android and independently persisted state. Explain the Number kept before-and-after PSS separate from peak claims and guest allocation separate from host RSS. Sequence Work into Verifiable Units led to isolated ownership proofs, final cycles, cleanup and one coherent tooling commit. The throughput checkpoint kept one writer for the coupled shell, seed, probes and recipe. Read-only grounding and alternative designs ran in parallel. Device work stayed serial. Ownership and cleanup gates preceded the final checks and publication. No product decision remains open in this PR. ## Dispatch ledger Every listed task was explicitly stopped and confirmed completed with no pending descendants. - m1-design-gpt-r1 ran on `codex_pool/gpt-6-astra`. Task `m1-design-gpt-r1`. - M1 cross-judge architecture ran on `codex_pool/gpt-6.1-sol`. Task `m1-crossjudge-r1`. - M1 how: Android harness grounding ran on `claudeAgent/claude-opus-5-5`. Task `m1-how-r1`. - m1-design-claude-r1 ran on `claudeAgent/claude-opus-5-5`. Task `m1-design-claude-r1`. - M1 Android harness adversarial review R1 Astra ran on `codex_pool/gpt-6-astra`. Task `m1-review-astra-r1`. - M1 Android harness fixes R2 1 ran on `codex_pool/gpt-6-astra`. Task `m1-review-r2-1`. - M1 Android harness implementation ran on `claudeAgent/claude-opus-5-5`. Task `m1-implementation-r1`. - M1 Android harness fixes R2 0 ran on `claudeAgent/claude-opus-5-5`. Task `m1-review-r2-0`. - M1 no-comments scope review ran on `claudeAgent/claude-opus-5-5`. Task `m1-no-comments-r1`. - M1 final fix review R3 gpt-6-astra ran on `codex_pool/gpt-6-astra`. Task `m1-review-r3-1`. - M1 final fix review R3 claude-opus-5-5 ran on `claudeAgent/claude-opus-5-5`. Task `m1-review-r3-0`. - M1 independent final Android cold verification ran on `claudeAgent/claude-opus-5-5`. Task `m1-runtime-r3`. - M1 Android harness adversarial review R1 Opus ran on `claudeAgent/claude-opus-5-5`. Task `m1-review-opus-r1`. - M1 last setup lifecycle review claude-opus-5-5 ran on `claudeAgent/claude-opus-5-5`. Task `m1-review-r4-0`. - M1 last setup lifecycle review gpt-6-astra ran on `codex_pool/gpt-6-astra`. Task `m1-review-r4-1`. - M1 final supervised lifecycle cold verification ran on `claudeAgent/claude-opus-5-5`. Task `m1-runtime-r4`. - M1 final decision trail audit ran on `claudeAgent/claude-opus-5-5`. Task `m1-trail-final`. The final two download repeats, commit, evidence publication and PR handoff ran in-thread because they use the existing deterministic commands and require no additional source changes.