Y2Linux / Y2Reborn 1.0 code review — 2026-10-17/18 (checks and six area reviews complete)
Fixed in Beta 8.0 (Baseline 75, 2026-10-18): B1, B3, B4, B5, B6, B7, B8, P1, P2, R2, R4, R5, R6, R7 (report). B2 kept strict (only v3.2.0 hashes; documented loudly). Open: R1 (large-file split), R3 (owner decision).
Companion to V1-RELEASE-READINESS. Read-only: nothing in either repository was edited, built, committed or sent to the player. The automated checks ran on 2026-10-17. The six area reviews that session launched never reported; they were redone by hand on 2026-10-18 (Y2Linux e771867, Y2Reborn a0b1a50), one time-boxed pass per area over the code that carries the most risk (scope per area under Coverage). Two findings were reproduced on the host in a scratch directory; no hardware evidence was taken.
Checks run
| Check | Command (from repo root) | Result |
|---|---|---|
| Rust formatting | cargo fmt --all -- --check (Y2Reborn, Rust 1.99.0) | clean |
| Clippy, strict | cargo clippy --workspace --all-targets -- -D warnings | clean, 0 warnings |
| Tests | cargo test --workspace | 26 binaries, 514 passed, 0 failed, 6 ignored |
| Clippy pedantic+nursery (reported separately) | cargo clippy --workspace --all-targets -- -W clippy::pedantic -W clippy::nursery -A missing_errors_doc -A missing_panics_doc -A module_name_repetitions -A doc_markdown | ≈1 900 warnings over the workspace (reborn-ui 909, reborn-platform 357, reborn bin 152, reborn-core 112, reborn-library 56, reborn-media 44, observability 36, graphics 31, audio 20, control 5). Top lints: unseparated literals 220, suboptimal_flops 151, must_use 143+96, usize→f32 precision 130, const fn 88. Worth a look: 27+27 usize↔i32 casts that wrap on the 32-bit target, 16 u128→u64 truncations, 18 "unchecked Duration subtraction" (all verified safe, see below), 25 redundant clones. No panic-class finding confirmed from the lint output alone. |
| Y2Linux host suite | sh tools/development/host-tests.sh (build out/build) | Ran 793 tests, OK (uMTPrd stat warnings on .. are harmless) |
| Docs links/catalog | python3 tools/development/check-docs.py | Y2Linux 392 pages, 4 367 links, 1 problem (catalog omits docs/planning/LASTFM-SCROBBLE-PLAN.md); Y2Reborn 68 pages, 0 problems |
| shellcheck | shellcheck -S warning over 70 project scripts (init.d, overlays, initramfs, tools) | 17 warnings, none a bug: SC2034 unused loop variables (n, attempt, round…), SC1007 on intentional CDPATH= cd --, one SC2046/SC2155 in wpa-event:9 (export Y2_DHCP_GENERATION=$(cat …) — single-word file, harmless) |
| clang-tidy on Reborn C (2026-10-18) | clang-tidy -checks='-*,clang-analyzer-*,-clang-analyzer-security.insecureAPI.*,bugprone-*,…' on reborn-audio/native/audio.c, reborn-media/native/media.c, reborn-graphics/native/graphics.c with the host pkg-config flags | 4 analyzer reports, all false positives on reading (media.c:1133 m->io is set on both open paths; :1612/1629/1680 id is set whenever read_image_file succeeds; :1731 loop stops at the terminator). The rest is style (signed bitwise, assignment in if, implicit void * casts). |
| cppcheck / ruff / pyflakes | — | not installed on this host; not run |
Findings
Severity: high = users lose something or a release feature fails in normal use; medium = fails in a plausible situation; low = narrow or defensive. Status: confirmed = reproduced or traced end to end; plausible = mechanism confirmed, trigger not yet seen on a player.
Bugs
| ID | Repo | File:line | Sev. | Status | Trigger / impact | Fix | Effort |
|---|---|---|---|---|---|---|---|
| B3 | both | Y2Reborn crates/reborn-library/src/lib.rs:1268-1271, app/reborn/src/main.rs:4219-4227, :3443; Y2Linux tools/platform/y2_platform/update.py:571, :542-546 | high | confirmed (host repro) | One song FFmpeg cannot open rolls back every update. A decode failure sets the source's complete=false; the scan that Reborn starts on every launch then ends in LibraryScanFailed("scan incomplete; retained existing entries (N traversal failures)"), so rebornctl status carries a non-null library.error. The failed file never gets a row, so it is decoded (and fails) again on every scan and the error never clears. health_ack treats any library.error as not healthy; after 180 s it calls core rollback + prepare, and the recovery restores the previous release. Trigger: any file with a supported extension that does not decode — truncated download, 0-byte or mislabelled .mp3, an .m4a with an unsupported codec. Host repro 2026-10-18: headless target/release/reborn with one valid WAV and one garbage broken.mp3 → "library":{"error":"scan incomplete; retained existing entries (1 traversal failures)","scanning":false,"tracks_loaded":1} at uptime 9 s. The owner's player has never hit it because the library decodes cleanly. | Both sides: (1) health_ack should require a live library worker, not a clean scan — e.g. accept library.error that starts with scan incomplete, or better a Reborn-owned healthy field (R7). (2) Reborn: count decode failures separately (stats.unreadable), keep complete, and mark the failed path seen (INSERT OR IGNORE INTO scan_seen … WHERE path=?) so its old row survives; show "N files could not be read" as a notice, not an error. Add a library test with one undecodable file and a Y2Linux fixture for health_ack. | S |
| B4 | Y2Reborn | crates/reborn-library/src/lib.rs:1268-1271, :365-371, :1488-1497 | medium | confirmed (code trace) | Same root: songs deleted over USB stay in the library. Pruning runs only for a complete source, so one undecodable file on Internal Storage or the SD card stops every later scan from retiring rows of deleted files. They keep showing and fail to play until the broken file is removed. | The B3 Reborn fix (mark the failed path seen, keep complete). Test: undecodable file + a deleted file → the deleted row is retired, the undecodable one is kept. | S (with B3) |
| B5 | Y2Linux | tools/platform/y2_platform/service.py:193-194, media.py:64, common.py:62-70, :100-104 | medium | plausible (mechanism reproduced on host) | An SD card whose read-only check takes longer than ≈7 s never mounts. The service loop runs y2-platform media reconcile with timeout=7, but inside it the admission check (fsck.fat/fsck.exfat/e2fsck -n) may take 60 s. command() starts every child in a new session and kills only that process group, so the killed reconcile leaves its checker running. media-lifecycle.json is never written, the next pass (3 s, 10 s with the screen off) starts another Python + checker, and the card stays "not available" while checkers pile up and keep the CPU and card busy. Host test 2026-10-18: a parent command(..., timeout=3) around a child that runs command(['sleep','20'], timeout=60) returns command_timeout after 3.0 s and sleep 20 is still running. Trigger: a large FAT32/exFAT library or a slow card plus the Python start-up on the Y2; no timing for the owner's card is on record. | Run reconcile as a background child like daily_update_check (poll, no 7 s limit), or give it a limit above the checker's; make command() kill the whole tree (prctl(PR_SET_PDEATHSIG) in a preexec_fn, or keep nested commands in the caller's group). Device test with the largest card available. | S |
| B6 | Y2Linux | kernel/patches/0065-y2-musb-pio-progress.patch y2_rx_mode1_start (patch lines ≈110-140) | low | plausible | Multi-packet receive start: after three DMAMODE kicks (≈60 µs) without movement the channel is aborted and the CPU reads the waiting packet, assuming "nothing has reached memory". Nothing re-reads the DMA address after channel_abort, so if the engine starts in the last microsecond the request loses or duplicates part of one packet without an error. Never observed (m1_kick and m1_fallback stayed 0 over the 1 GiB hashed uploads). Related: the kick exists only at start; a mid-transfer stall has no re-kick and would end only when the host cancels. | After the fallback channel_abort, sample y2_hsdma_moved(); if anything moved, give the request back with -EIO (the host retries) instead of the PIO read. Keep m1_kick/m1_fallback in the cable-pull and sleep tests (A8). | S |
| B7 | Y2Reborn | app/reborn/src/main.rs:3335-3349, crates/reborn-core/src/lib.rs:1259-1266 | low | plausible | After a rollback, an older Reborn that cannot parse the session (a newer SESSION_SCHEMA_VERSION, or a new enum variant inside Settings/AudioOutput) starts with AppModel::default() and its first checkpoint overwrites session.json: volume, skin, EQ, brightness, codec choice and the queue are lost, also for the later re-update. More likely now because B3 causes rollbacks. | Keep a rejected session as session.json.rejected-<stamp> (as the database quarantine does) and parse settings on its own with #[serde(default)] per field and unknown variants mapped to the default. | S |
| B8 | Y2Linux | initramfs/production/recovery.sh:57-66 | low | confirmed (by design) | recovery_stuck (unknown state, RescueRequired, an installer crash) keeps PID 1 in a console loop with the message on screen and never powers off; MISC stays boot-recovery, so the player drains to the PMIC cut-off and returns to the same screen after charging. Data is safe; the battery is deep-discharged. | Power off after a few minutes on battery (keep the request); stay on only while a charger is connected. | S |
Earlier findings that stay open: B1 (tools/production/package.py:116 ships the historical production-install.md as the user's install.md, medium, confirmed) and B2 (tools/platform/y2_platform/radiofw.py:277-311 accepts only the v3.2.0 FM firmware hashes, medium, confirmed); details in the readiness file (A3, A4).
Performance
| ID | Where | Note | Effort |
|---|---|---|---|
| P1 | Y2Linux service.py:193-194 | Part of B5: while a card check overruns, a full Python interpreter plus a filesystem checker start every 3–10 s. The Fix02 note in the same file measured that a recurring interpreter start alone held schedutil at 1040 MHz. | with B5 |
| P2 | Y2Reborn reborn-library/src/lib.rs:1268 | Part of B3/B4: every undecodable file is opened by FFmpeg again on every scan (no negative cache). Cheap for one file, linear in broken files on each boot. Remember the failure by (path, size, mtime). | with B3 |
| P3 | Y2Linux 0065 y2_rx_mode1_end | Up to 50 × (2 µs + register reads) ≈ 100–150 µs in hard IRQ with musb->lock held, once per received transfer (its closing short packet). Negligible at the measured ≈1 450 IRQ/s; noted so nobody adds work there. | — |
Not measured in this pass: per-frame allocations in reborn-ui (7.3 measured 62 fps median, no evidence of a problem), platform polling intervals (wifi 4 s, avrcp 3–5 s, refresh 30 s), ALSA period 512 / buffer 4096 frames.
Refactoring
| ID | Repo | Where | Status | Note | Effort |
|---|---|---|---|---|---|
| R1 | Y2Reborn | app/reborn/src/main.rs (5 872 lines), playback.rs (2 938), crates/reborn-ui/src/lib.rs (3 143), reborn-library/src/lib.rs (2 822), reborn-platform/src/bluetooth.rs (2 594) | confirmed sizes | Five files hold a third of the Rust code; the pedantic pass concentrates there. Seams seen in this pass: in main.rs the runtime loop vs storage/scan glue (≈4 190-4 760) vs status/diagnostics JSON (:2821); in reborn-library the scanner walk (:1310-1500) vs the SQLite worker (:200-900) vs search. | L, low risk with the 514 tests |
| R2 | Y2Linux | docs/release/Y2-COMMUNITY-BETA-FEATURE-AUDIT.md, docs/architecture/production-*.md | confirmed | Historical documents are still wired into packaging (B1) and named as current in the audit request; mark retired or move. | S |
| R3 | Y2Linux | buildroot/configs/y2_production_defconfig:33-99 | confirmed | Debug/measurement packages (strace, iperf3, memtester, evtest, i2c-tools) ship in production images. Move behind the dev defconfig. | S |
| R4 | Y2Reborn | crates/reborn-audio/native/audio.c:85 | confirmed, no user impact | profile_section_has falls back to an unquoted strstr, and the profile's rates are bare numbers, so 8000 matches inside 48000. The hardware still refuses (the AFE offers only 44.1/48 kHz, kernel/audio/mt6582-afe.c:34), so the gate is weaker than it reads. Parse the arrays (or match delimited numbers). | S |
| R5 | Y2Linux | tools/platform/y2_platform/common.py:62-104 | confirmed | command() can only kill its direct child's group (B5). A tree-kill helper (or PR_SET_PDEATHSIG) removes a whole class of orphaned helpers; health-ack (8 s), space --cleanup (2 s) and update auto-check run under the same rule. | S |
| R6 | Y2Linux | kernel/platform/storage-policy.h:43-100, internal_media.py:39-40 | from the handoff, not re-measured | The eMMC guard refuses ext4's WRITE_ZEROES/erase inside p8 (mmc_erase: group start error -30 at start; lazy inode-table init then gives up). Harmless, but noisy: advertise no erase/write-zeroes for the internal card, or make Y2MEDIA with lazy_itable_init=0,nodiscard. | S |
| R7 | both | Y2Linux update.py:549-573, Y2Reborn main.rs:2821 | confirmed | The update health rule reads Reborn's internal status fields (library.error, graphics.available, uptime_seconds). B3 is what happens when one side changes meaning. Have Reborn publish one explicit healthy verdict (with its reasons) in rebornctl status and test the contract in both repos. | S |
Verified not a problem
Kernel (Y2Linux):
- Multi-packet receive (0065), traced against upstream
rxstate()/musb_g_rx(): short packet and ZLP end (twoagreeing samples, abort, unmap before the CPU writes the tail, exact
actual), completion at the programmed length, the 1 MiB channel-limit continuation (goto y2_next), the one-packet completion with a waiting second buffer, the fallback to PIO for a short first packet or a request shorter than one packet, andrx_dmaswitched off on a mapped request (PIO, never mode 0).unmap_dma_buffernow also unmaps afternuke()dropped the channel; zero-length requests are never mapped. ECM ends frames on a short packet or ZLP (usbnet pads otherwise). - eMMC write guard (
storage-policy.h): overflow-checked ranges; writes only to ANDROID, USRDATA, p8 up tothe exposed size, the boot image the running kernel did not start from, and the 4 KiB MISC message; CMD6 only for volatile EXT_CSD fields and
PARTITION_CONFIGaccess bits; erase, sanitize, RPMB, boot, write-protect, lock, FFU denied. - Charging (
charge-limits.h,charger-policy.h,mt6323-charger.c): 4.175 V target with a separate 4.2 Vguard, time budgets on boottime, die-temperature ladder; every read/mismatch/late pet inhibits and latches a fault; an unverified stop keeps the AP awake and leaves the PMIC watchdog armed; faults clear only on verified removal.
- BTIF RX acknowledges before reading the write pointer (Beta 2 fix intact); threaded IRQ stop waits for the handler.
- Thermal zone resume is fixed (
8ba95b3,y2_thermal_resume).pm-journal.cwrites retained SRAM only.
Platform (Y2Linux):
- Internal Storage setup (
internal_media.py): every phase (staging → formatting → migrating) is recorded onY2DATA with file + directory fsync before the next starts; kept files are copied to a temporary name, fsynced, re-read from the medium (
POSIX_FADV_DONTNEED) and hash-compared before publishing; originals are deleted only afteros.sync(); formatting requires the recorded partition/serial; every step is idempotent on resume. atomic_json(unique temp,O_EXCL|O_NOFOLLOW, optional fsync + directory fsync); mount-option checks useparsed lists (
errors=remount-rodoes not read asro).
Update and recovery (Y2Linux tools/update/core.c, initramfs/production/recovery.sh):
- Nothing in a manifest is acted on before the Ed25519 check; sequence anti-rollback including
accepted.json;payload contracts and gzip bounds (size, single member, raw size, ext4 extent) enforced while decoding.
- Install order: verify package → back up root + BOOTIMG (old copies deleted first, new ones read back and
hashed) →
Installing→ root with CRC readback from the medium (BLKFLSBUF) → BOOTIMG with fsync and readback →PendingHealth→ clear MISC. An interruption at any point resumes as a restore; restore checks both backups before its first write. Journal writes are durable (temp, fsync, rename, directory fsync). preparewrites RECOVERY only while BOOTIMG is intact, verifies it before setting MISC;boot_checkgives a newsystem two boots, refuses a fourth recovery request, and clears a request LK ignored.
- Every recovery outcome is handled; a low battery waits ten minutes for a charger and powers off with the request kept.
Reborn:
- Panic surface: 19 non-test
unwrap/expect/unreachablesites, each guarded by the line before (e.g.storage.rs:215after the empty-path check at:178,draw.rs:770afteris_some()). - Persistent state uses one
atomic_write(temp,sync_all, rename, folder fsync) — 13 call sites including thesession and playlists; database corruption is quarantined before recreation.
- FFI:
RbParams/RawParamslayouts match; ALSA-EPIPE/-ESTRPIPE/-EINTRhandled inwrite_error,-EAGAINin the pacing; non-blocking drain on pause.
media.c: 32 MiB read budget per inspection, 16 MiB image and pixel limits, MJPEG low-res decode, file-onlyprotocol whitelist, radio via pipe with a 45 s stall limit.
- Scrobble timing uses
CLOCK_BOOTTIME(reborn-platform/src/scrobble.rs:152). - Earlier:
Instant::now() - Duration::from_secs(30)atapp/reborn/src/main.rs:3689,reborn-platform/src/bluetooth.rs:1273,1332,1415,1582,avrcp.rs:249,wifi.rs:593does not panic on Rust 1.99 (Timespec::checked_sub_durationonly fails on i64 overflow).gh release create … --latest <assets>publishes only after all uploads. Update anti-rollback uses the manifest sequence, so1.0.0after1.0.0-beta.7.11works.
Coverage
| Area | Read in full | Read in part / skipped |
|---|---|---|
| Kernel/drivers | patched musb_gadget.c RX paths, musbhsdma.c abort/moved, kernel/platform/usb.c DMA selector, storage-policy.h, storage-layout.h, charger policy/limits/driver run loop, btif.c, thermal resume | rest of usb.c, spm.c, pm-journal.c (persistence only), audio drivers; the vendor Wi-Fi driver (kernel/platform/connectivity/wifi, ≈130 k lines) not reviewed |
| Platform daemon/tools | internal_media.py, media.py, common.py, service.py, update.py | usb.py (views), observe.py (mountinfo); power.py, product_sleep.py, maintenance.py, network.py, radiofw.py beyond B2 not reviewed |
| Release/update/recovery | core.c (all of verify/backup/restore/recover/prepare/boot_check/main), recovery.sh | release.py, package.py beyond B1, recovery-ui.c not reviewed |
| Reborn app + platform | session restore, storage pinning, scan triggers and results in main.rs, panic sites | bluetooth.rs, wifi.rs, radio.rs, lastfm.rs, avrcp.rs read only for panic sites |
| Reborn UI/core/library | scanner walk, reuse probe, prune, atomic_write, AppModel::restore | rendering, skins, search index not reviewed |
| Reborn native C + FFI | audio.c, native.rs FFI and write path, media.c I/O and artwork, clang-tidy over all three C files | graphics.c only through clang-tidy |
Top 10 (both files)
- B3/B4 — one undecodable song rolls back every update and stops pruning: fix the health rule and the scan
accounting before the next release (S, no hardware needed; then one OTA on a player with a broken file).
- A1 — second-unit sleep drain (#42): capture plan, A/B nights, mitigation decision.
- B5 — SD admission under the 7 s reconcile limit; device test with a large card.
- A3/B1 — ship the real INSTALL guide in the first-install zip.
- A5/A6 — fix stale tags and "Beta" wording in readme, BUILDING, INSTALL, STORAGE, release notes template.
- A8 + B6 — harden the mode-1 fallback, then the cable-pull, sleep/wake and Windows tests of receive DMA.
- A11 — one consolidated device-check pass closing the 34 owed items.
- A4/B2 — firmware-version policy for the Innioasis package.
- A2 — an external install-from-stock and build-from-source run.
- A7, then R7/B7 — toolchain sources in the release; an explicit health contract and a session that survives a rollback.
Not done
No hardware evidence (rules of the audit). The areas and files marked "not reviewed" under Coverage, the vendor Wi-Fi driver, and per-frame/polling performance measurements remain open. cppcheck, ruff and pyflakes are not installed on this host.