Skip to content

← Firmware Downloads

Post-install setup · Reborn (Y2Linux) · Y2

Platform Rust: the remaining refactor, for one agent (written 2026-10-30, after test build 175)

Debian-based Linux with a fast clickwheel interface for the Innioasis Y2.

Instructions published by the project, shown here word for word.

Platform Rust: the remaining refactor, for one agent (written 2026-10-30, after test build 175)

One agent works through this alone, in order, without spawning others. Everything needed is here or linked. Read this file, then §1 and §3 of PLATFORM-RUST-REFACTOR-PLAN.md (the contract and what was already done), then start at §4 below.

Superseded for the next work by PLATFORM-RUST-IDIOMATIC-PLAN.md (owner 2026-10-30: byte identity dropped; §2–§3 here still apply where that plan does not replace them).

0. Status of this pass (2026-10-30/31, one agent overnight; updated as items finish)

Baseline: byte goldens recorded from 2e68d63e (test build 175's code). Owner decisions for §5 given up front (2026-10-30): 1 strictest equality that keeps every golden; 2 enums only, no typed shutdown intent; 3 do not merge the volume observers, share constants/helpers only; 4 keep blkid in status and y2-bt-observe; 5 keep the reports Py type; 6 keep the bool tolerance of status_query_failures; 7 keep renewing network.json/time-status.json on a cheap timer (no Reborn change).

1. Where it stands

What still follows Python (2026-10-30 count, non-test code): ~1,650 serde_json::Value mentions, ~910 json!, ~500 .get("field") lookups against ~70 typed Serialize records; ~170 &'static str states.

grouplinesValuejson!Python emulationtyped records
reports7,7002223544 (the Py value type)3
observation6,300539300560
service6,000215117651
suspend5,000207813521
storage4,300132993312
connectivity3,900132143156
power3,6001152269

The owner's view (2026-10-30): the gain of more typing is correctness and maintainability, not speed. Do it selectively: first the records written by one component and read by another, then a group when it is changed anyway. Leave stable, rarely touched code (report building, bench) unless an item below names it.

2. Rules (do not break)

3. Method (what made 173–175 safe)

  1. Record the byte baseline once from the commit you start at:

    tools/development/platform-gate.sh --baseline <dir outside the repo> (e.g. the session scratchpad).

  2. After every change: tools/development/platform-gate.sh <that dir> must print GATE OK before the commit

    (fmt, clippy with no warning, armv7 check, all cargo tests, byte comparison of every golden case).

  3. A change that deliberately alters recorded calls (a process no longer started) or written files:
    • re-record: `Y2_PLATFORM_RUST=$PWD/platform/target/debug/y2-platform Y2_RECORD_GOLDENS=1 PYTHONPATH=.:tests

      python3 -m unittest tests.test_platform_differential tests.test_platform_devices tests.test_platform_reports tests.test_platform_observation tests.test_platform_suspend tests.test_platform_power_rpc`;

    • review the git diff of tests/platform_goldens/ (only calls/tree may change; compare old/new with a

      small script, as in the 174 commits);

    • check that the gate's differing cases are exactly those goldens, copy them from <dir>.now/ into the

      baseline and note why;

    • say so in the commit message.
  4. Before a build: tools/development/host-tests.sh (576 tests) must pass.
  5. Traps seen: cargo fix removes imports only a use super::* test module needs (give the test module its own

    use); a regex rewrite also catches the body of a new impl From/trait method (check for self-recursion); lock helpers retry 0.2 s in test builds (lock::settle), so timing asserts need slack; take kernel ABI numbers from a C compile, not from parsing headers.

4. The work, in order

Each item: where, what the Rust shape is, risk, how to verify. Sizes: S < 1 h, M ≈ half a day, L ≈ a day.

A. Typed records shared between components (do first; S–M each, byte-identical)

Pattern: a #[derive(Serialize, Deserialize)] struct per record, defined once (writer's module or y2p_contract), field order = today's written order (records written by atomic_json are sorted anyway, so order is free there; printed unsorted records keep insertion order). Readers use a lenient load (missing or odd fields → the same fallback as today) — the pattern is connectivity/record.rs.

  1. /run/y2/ssh.json: writer devices/ssh.rs (struct State), reader observation/system.rs fn system

    (get_eq(&ssh, "boot_id", …), fresh_seconds(… "monotonic_s" …)). One SshRecord with is_this_boot() / fresh(seconds).

  2. /run/y2/network.json: written by service/daemon.rs (the Wi-Fi pass), read by

    connectivity/daemon/supervisor.rs and connectivity/wifi/recovery.rs (untyped reads), observation, Reborn. query_failures keeps a bool from old records (owner decision to drop that tolerance — ask, else keep).

  3. /run/y2/power.json readers: suspend/admission.rs fn battery_and_cable (reads state, battery,

    filtered voltage, percent, confidence untyped), storage/media/repair.rs battery check, reports upload. A lenient PowerState view next to power/daemon.rs PowerRecord.

  4. /run/y2/media-mount.json (the SD claim) and the Y2Media claim: the internal-media claim check exists 4

    times with different equality rules — observation/system.rs (internal_ready), devices/storage.rs, storage/internal.rs, service/radiofw/copy.rs. One typed claim + one ready(root). Byte-check carefully: the copies compare with py_eq vs == vs eq_int (differ only for odd values; pick the strictest that keeps every golden, note it).

  5. Update journal state: state_is(&status(root)) in service/update/{apply,check,health,staging}.rs builds

    the whole status (download.json, /proc/locks, last-check.json, channel) just for state; use update::journal_state and a JournalState enum. download.json has 7 json! shapes in staging.rs → one struct.

  6. USB views (service/usb/views.rs, built as Object and read back with py_text/truthy): a struct View

    in the same field order.

  7. previous-boot-evidence.json: written in observation/evidence/mod.rs (16 inserts), read in

    observation/system.rs, reports/checks.rs, reports/diagnostics.rs.

B. State enums (S each, byte-identical: unit variants serialize as the same text)

C. The status snapshot as types (L; byte check needed)

observation/snapshot/*.rs and observation/system.rs build ~270 maps; readiness.rs and health.rs read them back by key (18 lookups each). Status and health print sorted, but **diagnostics export projects the snapshot in insertion order**, so struct field order must equal today's insertion order. Go section by section (power, thermal, memory, cpu, idle, wifi, storage, system, readiness), one commit each.

D. Control flow (M each, byte-identical)

E. Remaining duplicates (S each unless noted)

F. Remaining unsafe (173 blocks; M)

observation/bench/scratch.rs (11, raw fds: give it OwnedFd/BorrowedFd like devices/transfer/fd.rs), devices/transfer/fd.rs read/write (a ManuallyDrop<File> over the borrowed fd removes two), power/unix.rs accept/peer credentials, suspend/helper/kernel.rs. Leave y2p_core/reports_sqlite.rs (behind a clean API).

G. Process starts that remain (M each; device test required)

H. The Python-emulation layer (decide per use)

Keep where it makes the output (py::os_error texts, Python float repr, json.dumps spacing, argparse error texts). Replace with plain Rust where it is not visible: py::truthy/py_eq on values the code itself wrote, python_int for sysfs/procfs numbers (trim().parse() gives the same; pid files need a check). The reports group's Py value type tolerates odd old records exactly as Python did; replacing it is an owner decision (refuse odd values instead of tolerating them).

I. An event-driven service daemon (L; the one item left with a real CPU gain; device tests required)

Owner (2026-10-30): "nice to have, put it in the handoff". Do it after A–H, in its own commits, never mixed with a structural change.

Today: service/daemon.rs fn serve runs a full pass every 3 s lit / 10 s dark (INTERVAL_S, DARK_INTERVAL_S), waking early only for supplicant events and signals (monitor.wait(.., wake.fd())). Each pass: display_dark (backlight glob + 2 reads), usb.tick (UDC glob and state, usb-mode.json, block and mount snapshots, write windows), the Wi-Fi look (cheap now: direct reads), coexistence, the 30-s radio policy, network.json and time-status.json writes, the media reconcile/verify, firmware and report job slots, NTP (60 s) and the maintenance pass (30 s lit / 300 s dark). Measured 7.7 s CPU/h dark on 174; the expected gain is about half of that, plus faster reactions (USB, SD card, Wi-Fi).

Shape: one poll set and one deadline:

Constraints:

Verify on the player:

J. Smaller idle items (S–M)

5. Owner decisions to ask for (only when the item comes)

  1. A4: which equality the Y2Media claim uses when the copies disagree on odd values.
  2. B: a fully typed shutdown intent (changes a resumed intent's reply order).
  3. E: merging the three volume observers (byte changes where their records differ).
  4. G: ext4 superblock instead of blkid in status; gating y2-bt-observe.
  5. H: replacing the reports Py type (refuse odd old values).
  6. A2: dropping the bool tolerance of status_query_failures.
  7. I: keep renewing network.json/time-status.json on a timer, or change Reborn's 15-s freshness rule with

    the event-driven daemon.

6. Build, install, verify

tools/development/host-tests.sh
# bump "update_sequence" in tools/production/release.json above the installed one, commit (tree clean!)
Y2_STOCK_PACKAGE=$PWD/donorSource/y2_v3.2.0_FM-20260813 python3 tools/production/release.py build --label 176
Y2="ssh -F /dev/null -o HostKeyAlias=y2linux-production-v1 -o BatchMode=yes -i ~/.ssh/y2linux_ed25519 [email protected]"
$Y2 'rm -rf /data/updates/owner-175; mkdir -p /data/updates/owner-176'
scp -F /dev/null -o HostKeyAlias=y2linux-production-v1 -i ~/.ssh/y2linux_ed25519 -q out/release-176/ota/* [email protected]:/data/updates/owner-176/
$Y2 'y2-platform update stage --package /data/updates/owner-176; y2-platform update apply'
# wait (one ssh after ~3 min, not a polling loop): update status must say Acknowledged
$Y2 'sh -s' < tools/development/platform-device-check.sh

A packaging step refuses an uncommitted tree; a failed build resumes on the next run. Idle measurement: one script on the player (per-process utime+stime from /proc/<pid>/stat and Reborn's threads from /proc/<pid>/task/*/stat, processes from /proc/stat, before and after sleep 300), started with nohup, read back with a single sleep 330; ssh … — every ssh session itself starts processes. Measure after ≥ 30 min uptime (start-up work inflates the first minutes). Never pkill -f a pattern that occurs in your own ssh command line.

Afterwards: an entry in roadmap-gap-audit.md (as for 173–175), the START HERE block in NEXT-SESSION-HANDOFF.md, the refactor plan's status, and the memory file y2-platform-rust-port-phase0.md.

7. Still owed from 173–175 (not code)

Published by the schulzcode/y2linux project [1] [3]