            /* ── the tree in the car, with the app FORCE-STOPPED ───────
               LAST in the phone leg, and it kills the app on purpose. The
               claim round fifty makes is not "the browse tree is correct"
               but "the browse tree is there when the app is not" — a car
               that has to be handed a running phone first is no use to
               anybody — so every check below is taken against a process the
               SYSTEM built for the browse, with no Activity, no Tauri and no
               WebView in it.

               What drives it is round forty-nine's prober: a component in
               its own process that `am start-service` can start cold,
               connects to our own MediaLibraryService as a framework
               MediaBrowser and walks the tree. What is ASSERTED is the
               service's own log of what it answered (see autoNodes()) —
               which is the one reading a bug in the prober cannot fake.

               Nothing here can say whether AUTO renders any of it. That
               needs the Desktop Head Unit or a car, and ANDROID.md says so.
               What this proves is the half that can be proved from here,
               and it is the half that has always been broken in this port
               when something was. */
            {
                const say = (...a) => { try { return adb(...a).toString(); } catch (e) { return "ERR " + e.message; } };
                const PROBE = "com.soulstream.app/com.soulstream.mediasession.AutoProbeService";
                const pidBefore = (say("shell", "pidof", "com.soulstream.app").trim().split(/\s+/)[0]) || "";
                /* the WebView dies with the process; drop the socket first so
                   the walk's own finally has nothing to trip over */
                if (cdp) { cdp.close(); cdp = null; }
                say("shell", "am", "force-stop", "com.soulstream.app");
                await sleep(3000);
                say("logcat", "-c");
                const launched = say("shell", "am", "start-service", "-n", PROBE).trim();
                /* Polled, never fixed-waited. A cold `dlopen` of a ~100 MB
                   debug .so plus the first SQLite open on a phone is not a
                   number anybody here has measured, and reading a slow
                   answer as a failure has cost this project a round twice.
                   Eight nodes is the shape the assertions want — a root,
                   four tabs and three levels — and sixty seconds is the
                   ceiling, not the wait. */
                let rows = [];
                for (let i = 0; i < 60; i++) {
                    await sleep(1000);
                    rows = autoNodes();
                    if (rows.length >= 8 && rows.some((r) => r.node.startsWith("ss:root"))) break;
                }
                const seen = rows.map((r) => r.node);

                /* ── the root: within the limit the browser hinted, and all
                   browsable. Google's own rule, and Auto's: only a browsable
                   item may be a root tab, and a root that overflows the
                   limit has its extra tabs quietly dropped or buried. `lim`
                   is what LibraryBridge read out of the root hints, so a
                   hardcoded four would show up here as a limit that never
                   changes rather than as nothing at all. */
                const root = rows.find((r) => r.node.startsWith("ss:root")) || null;
                const rootOk = !!root && root.lim >= 1 && root.n >= 1 && root.n <= root.lim
                    && root.br === root.n && root.pl === 0
                    && root.uniq === root.n && root.titled === root.n
                    && root.kinds === "tab";
                if (!rootOk) problems.push("EXPECT auto-root " + JSON.stringify({ root, launched, seen }));
                console.log((rootOk ? "  ok  " : " FAIL ") + "auto-root".padEnd(16) + " "
                    + JSON.stringify(root ? { n: root.n, lim: root.lim, br: root.br, pl: root.pl, kinds: root.kinds, ms: root.ms } : { root: null, launched }));

                /* ── the four tabs: each loads, each row has a stable id of
                   its own and a title, and every cover the library HAS
                   became a content:// URI (`fail` is the count that did
                   not — a provider root that does not reach the artwork
                   lands there, and nowhere else). `art >= 1` rather than
                   `art === n` on purpose: an album with no art_path
                   genuinely has no cover, and demanding one would be a
                   check that lies about the library. */
                const TABS = ["ss:tab:recent", "ss:tab:albums", "ss:tab:artists", "ss:tab:playlists"];
                const tabs = TABS.map((id) => rows.find((r) => r.node === id) || null);
                const tabOk = (t) => !!t && t.n >= 1 && t.uniq === t.n && t.titled === t.n && t.fail === 0 && t.art >= 1;
                const tabsOk = tabs.every(tabOk);
                const tabReport = TABS.map((id, i) => [id.slice(7), tabs[i] ? [tabs[i].n, tabs[i].art, tabs[i].fail, tabs[i].ms] : null]);
                if (!tabsOk) problems.push("EXPECT auto-tabs " + JSON.stringify({ tabs, seen }));
                console.log((tabsOk ? "  ok  " : " FAIL ") + "auto-tabs".padEnd(16) + " " + JSON.stringify(tabReport));

                /* ── three levels deep, which is the brief. An album's
                   children are tracks and every one of them is playable and
                   nothing else; an artist's children are albums and every
                   one of them is browsable; and at least one album was
                   reached UNDER an artist rather than only under Albums,
                   which is the third level and the only clause here that
                   the Albums tab alone cannot satisfy. */
                const albums = rows.filter((r) => r.node.startsWith("ss:album:"));
                const artists = rows.filter((r) => r.node.startsWith("ss:artist:"));
                const lists = rows.filter((r) => r.node.startsWith("ss:playlist:"));
                const firstArtist = rows.findIndex((r) => r.node.startsWith("ss:artist:"));
                const deeper = firstArtist >= 0 && rows.slice(firstArtist + 1).some((r) => r.node.startsWith("ss:album:"));
                const depthOk = albums.length >= 1 && artists.length >= 1 && lists.length >= 1 && deeper
                    && albums.every((r) => r.n >= 1 && r.kinds === "track" && r.pl === r.n && r.br === 0 && r.uniq === r.n && r.titled === r.n)
                    && artists.every((r) => r.n >= 1 && r.kinds === "album" && r.br === r.n && r.pl === 0 && r.uniq === r.n)
                    && lists.every((r) => r.n === 0 || (r.kinds === "plitem" && r.pl === r.n && r.uniq === r.n));
                const depthSeen = { albums: albums.length, artists: artists.length, playlists: lists.length, deeper, kinds: rows.map((r) => r.node + ":" + r.kinds) };
                if (!depthOk) problems.push("EXPECT auto-depth " + JSON.stringify(depthSeen));
                console.log((depthOk ? "  ok  " : " FAIL ") + "auto-depth".padEnd(16) + " "
                    + JSON.stringify({ albums: albums.length, artists: artists.length, playlists: lists.length, deeper }));

                /* ── the covers, opened rather than believed. `open` is one
                   real openFileDescriptor against the first URI the service
                   minted: it is what separates "the FileProvider produced a
                   plausible URI" from "the file is there and readable
                   through it", and a files-path root — the one this round
                   nearly shipped — fails it while breaking nothing else.
                   It cannot prove the GRANT: the prober shares our uid and
                   would read the provider with no grant at all. Only a car
                   proves that, and grantUriPermission is the reason the line
                   exists in LibraryBridge at all. */
                const artOk = rows.length >= 1 && rows.some((r) => r.open === "ok") && rows.every((r) => r.fail === 0);
                const artSeen = { open: [...new Set(rows.map((r) => r.open))].join("|"), fail: rows.reduce((a, r) => a + Math.max(0, r.fail), 0), art: rows.reduce((a, r) => a + Math.max(0, r.art), 0) };
                if (!artOk) problems.push("EXPECT auto-art " + JSON.stringify({ artSeen, notes: rows.map((r) => r.node + " " + r.note) }));
                console.log((artOk ? "  ok  " : " FAIL ") + "auto-art".padEnd(16) + " " + JSON.stringify(artSeen));

                /* ── and COLD, which is the whole round. A new pid (the app
                   was force-stopped, so the one serving the browse cannot be
                   the one that was playing), no resumed MainActivity, and no
                   WebView devtools socket for it — the three things that
                   would each mean the app quietly came back and answered
                   from the warm path. */
                const pidAfter = (say("shell", "pidof", "com.soulstream.app").trim().split(/\s+/)[0]) || "";
                const acts = say("shell", "dumpsys", "activity", "activities");
                const resumed = acts.split(/\r?\n/).some((l) => /ResumedActivity/.test(l) && /com\.soulstream\.app/.test(l));
                const socks = say("shell", "cat", "/proc/net/unix");
                const devtools = !!pidAfter && new RegExp("webview_devtools_remote_" + pidAfter + "\\b").test(socks);
                const coldOk = /^\d+$/.test(pidAfter) && pidAfter !== pidBefore && !resumed && !devtools && rows.length >= 1;
                const coldSeen = { pidBefore, pidAfter, resumed, devtools, nodes: rows.length };
                if (!coldOk) problems.push("EXPECT auto-cold " + JSON.stringify({ coldSeen, launched }));
                console.log((coldOk ? "  ok  " : " FAIL ") + "auto-cold".padEnd(16) + " " + JSON.stringify(coldSeen));

                /* ── and how long each node took, as numbers, the way the
                   boot budget is read. Both bounds on purpose: an `ms` of -1
                   is a line that did not carry one, and a one-sided test
                   would sail straight through it. The FIRST node pays the
                   cold dlopen and the first SQLite open and gets a second
                   and a half; everything after it is a warm query and gets
                   400 ms. Every measured value is printed, because the first
                   real run may well move both numbers and a budget guessed
                   at twice is worse than one printed once. */
                const msAll = rows.map((r) => r.ms);
                const latOk = rows.length >= 1 && msAll.every((m) => m >= 0)
                    && msAll[0] < 1500 && msAll.slice(1).every((m) => m < 400);
                const latSeen = { first: msAll[0] ?? -1, rest: msAll.slice(1), hop: rows.map((r) => r.hop) };
                if (!latOk) problems.push("EXPECT auto-latency " + JSON.stringify({ latSeen, seen }));
                console.log((latOk ? "  ok  " : " FAIL ") + "auto-latency".padEnd(16) + " " + JSON.stringify(latSeen));

                /* put the phone back the way the walk found it: the app
                   open. It restores PAUSED (session restore's own rule), so
                   this starts nothing playing. */
                say("shell", "am", "start", "-n", "com.soulstream.app/.MainActivity");
                await sleep(2000);
            }
