Skip to content

← Firmware Downloads

Post-install setup · Reborn (Y2Linux) · Y2

Platform Rust refactor: from ported Python to idiomatic Rust

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

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

Platform Rust refactor: from ported Python to idiomatic Rust

Status (2026-10-30 morning): the follow-up work is tracked in PLATFORM-RUST-NEXT-AGENT-HANDOFF.md (§0 has the state: A–F done (with stated exceptions), G partly, I/J2 need the owner; test builds 176–179 on the player). Earlier status: done 2026-10-29: test build 171 (sequence 11160) is on the owner's player, Acknowledged. Both waves are merged on main; the resume-point and progress notes below are history. Owed: the owner's hardware checks on 171 (170 is the comparison point); open owner questions: Python wording left in some refusal texts, whether the remaining radios are restored after a failed Wi-Fi restore after sleep, whether service-daemon should skip odd records (now) or end with an error record, whether an unreadable queued error report should be dropped. Still open from the plan: event-driven daemons (§2.5, needs drain measurements), merging the Python text helpers that remain per group (suspend_text, power_text, observation util, reports pyre, storage_media), the reset plan and diagnostics input as Py, the duplicate journal code in suspend and power.

Progress (2026-10-29, stopped at the usage limit): wave 1 is merged on main (2f5b5075): the argparse emulation is replaced by crates/y2-platform/src/cli.rs (table-driven, y2-platform __arguments feeds platform-contract.py) and one shared core exists (y2p-core json::{Object, ObjectExt, dumps_sorted} with serde_json preserve_order, py::PyValue, mounts, Root::boot_id, record::atomic_json, lock, volume, sys libc wrappers, Runner/ Clock carried by Root with Root::fixture(..).with_answers(..)); goldens unchanged except the parser's deliberate ones. The golden suite sorts JSON before comparing: key order/spacing must be checked by comparing raw output against a baseline binary (wave 1 did this for all 1 063 cases). Next: wave 2, one agent per group (observation, connectivity, devices, storage, service, reports, power, suspend): adopt the core, typed records, tests from scripted to Root injection (then delete the thread-local and #[cfg(test)] seams), crash paths → refusal records, split long modules, merge the duplicated Python text helpers (suspend_text, power_text, observation util, reports pyre), per-tick work in the daemons, connectivity: same-lease renew no-op and the DNS fixture argv. Then host suite, build 171, OTA. Not built or installed yet; the player runs 170.

Resume point (2026-10-29, evening, usage limit near): wave 2 runs as eight agents in git worktrees under .claude/worktrees/agent-*, one branch per group. Merged on main: all eight groups (plus wave 1); next: the cleanup, checks, CONTRACT.md, build 171 and OTA below. Still on their branches (each commits per step; continue or merge what is there):

To resume in a fresh session: for each remaining branch, check git log main..<branch> and whether its worktree has uncommitted changes (git -C .claude/worktrees/<agent> status); merge finished branches into main (git merge --no-edit <branch>; conflicts so far were only test-module include lines and docs), or let a new agent finish a group from its branch with this plan's wave-2 brief (the step list in the progress note above). After all groups: delete crate::testing::scripted and the thread-local in y2p_core::command::testing, remove codec::not_iterable if unused, run cargo test/clippy/armv7, the golden suite and the byte check (tools/development/platform-raw-goldens.py against a pre-wave-2 build of main 342d08e0: only crash-path refusal, same-lease renew and DNS-argv cases may differ), update CONTRACT.md (refusal reasons, dhcp.json prefix_length/ routes, same-lease renew, parser), the host suite, then release.py build --label 171 (bump sequence above 11150 first) and install by staged OTA (PLATFORM-RUST-PORT-HANDOFF.md §5).

Quick wins after 171 (2026-10-29, test build 172): atomic_json no longer deletes after a successful rename; one y2p_core::text (Python's whitespace and line rules) instead of five copies; one Wi-Fi observer for `status wifiand the daemons (observationsnapshot::wifi::observe`, lenient for the daemons); one workload lease (commands::workload) instead of five; time-set shares time's status and RTC policy; the codec inventory refuses itself (Python's AttributeError text gone); the split modules import explicitly (no use super::* outside tests); stale Python references fixed in the living docs; one USB state, idle counters and sleep-diagnostics parts for the power journal and the suspend group (power::journal::{usb, counters, state}). Every step byte-identical against 171 (tools/development/platform-raw-goldens.py). Measured and skipped: sorting power.json while printing (≈3 µs per write on the PC, ~0.1–0.2 s CPU per hour on the player) and the second versions.json read per status (tens of µs) — not worth the code. Known: update_tests::space_cleanup_stale_package_and_cancel_free_the_data_volume failed once under a full parallel run (passes alone and in 8 later full runs; not in the release gate).

Duplication pass and idle gains after 172 (2026-10-29, test build 173): one y2p_core::paths (name, parent_name, realpath), y2p_core::digest (hex, sha256_hex), clock::wall, battery interpolate_int, update journal_state, time::record, sys::openat, json::python_error, suspend E::private_directory/E::atomic_json — every step byte-identical against 171. Left as they are, on purpose: u16/u32_at (different readers), reports' update_state (own error), suspend's pyjson vs y2p_core::pyjson::Py (parser vs value). Tests: the flaky update tests had two causes (a forked child holding .stage.lock: lock::try_fd got the test-build retry; the wake tests' process-wide SIGTERM during stage: testing::process_signals). Logic: the Wi-Fi look and ssh-daemon's usb0 address start no process on the player (connectivity::direct: wpa_supplicant control socket STATUS/SIGNAL_POLL, rtnetlink addresses and default route; below a fixture root the same fixture commands); power.json meant to be rewritten on change and at least every 2 s — no effect on 173 (10 writes in 10 s: the raw ADC voltage in the record changes every sample; see the next pass, P1); the service daemon's minute S43y2-time start only when ntp.pid names no live process; status --reborn reads Reborn's metrics from its control socket (Reborn's control thread answers them); Reborn dark and quiet: a full main-loop pass on an event or a due timer, at most every 5 s, watchdog every 5 s, observation 10 s, snapshot 30 s. Left on purpose: space --cleanup stays its own process (statvfs of the SD card can block in D state; that must not stop Wi-Fi), ssh.json keeps its 5-s renewal (status refuses one 10 s old).

Next pass — review of 2026-10-30 — DONE in test builds 174/175 except where marked left: P1, P2 and P8 done; P3 done for netlink, pidfd, kill/alive, new_session, dir_names, *at, unix sockets, BorrowedFd in transfer (left: the five poll loops, which round differently on purpose; the bench scratch layer); P4 done as per-target probes and the space policy in process (left: merging the three observers, byte change); P5 done for the USB tick's media.lock (left: synchronous health-ack until settled, a Job type for the child slots); P6 done for the usb0 address and one snapshot in diagnostics export (left: the cats, blkid in status, report children); P7 done for health's CheckState (left: the typed records and state enums, see "What still follows Python"); P9 done for iw's readback (left: rare wpa_cli/ip/date/rebornctl starts). Original findings:

What still follows Python (2026-10-30 count, non-test code): ~1,650 Value mentions, ~910 json!, ~500 .get("field") against ~70 typed Serialize records; worst observation (539 Value, 300 json!, 0 typed), then service and suspend; reports carries a Python value type (Py, 544 uses) for byte-identical bundles. Owner discussion: gains are correctness and maintainability, not speed; recommended selectively — type the records written and read across components (ssh.json, network.json, power.json readers, media-mount.json, the update journal, the status sections Reborn reads) and convert a group when it is next changed.

Original next-pass findings (2026-10-30, four read-only reviewers, spot-checked). Ranked by gain per risk; "byte" = needs the raw byte comparison, "owner" = needs an owner decision.

Owner decisions (2026-10-29, §7): replace the argparse emulation with a small declarative parser (exact option names and --opt=value, errors exit 2, help says y2-platform); every fake-traceback crash path becomes a refusal record (exit 1, JSON {"state": "Failed", "failure": …}); keep the exact JSON output format (json.dumps spacing, key order, Python float repr) — only the internal types are unified; a DHCP renew of the same lease changes nothing (no flush and re-add).

Owner (2026-10-29): put the Rust-only build on the player, review what works and what not, then "another pass regarding refactoring / code quality" because the code is "more like ported python and not really well written / optimised rust". This plan is written for a session that picks this up cold.

0. Read first

  1. PLATFORM-RUST-PORT-HANDOFF.md: layout, build/test/install commands, rules

    (git identity, no push or publish without the owner, OTA-only installs).

  2. platform/CONTRACT.md: who calls what (Reborn, scripts, the platform itself), the

    fixed defects, the golden suite.

  3. The top entry of NEXT-SESSION-HANDOFF.md and the device results of the newest test

    build in roadmap-gap-audit.md.

  4. AGENTS.md: this is an architecture change of the platform; repeat the roadmap/gap audit before starting and

    before a build that carries the result.

1. Where the code stands (2026-10-29, main after the Rust-only merge)

Device numbers to beat or keep (build 169): 4 daemons 15.2 MB RSS / 4.6 MB PSS, 19.6 s CPU per hour awake idle (power 9.1, service 8.1, ssh 2.4, connectivity 0.2), status --reborn --pss 0.48 s (mostly Reborn's own rebornctl metrics --json), restart trail 0.46 s, boot to application_ready 36.8 s.

2. What makes it "ported Python" (the debt, with locations)

Counts are rough (grep over non-test files, 2026-10-29).

2.1 Emulation of Python itself (≈3 200 lines)

Written so the output stayed byte-identical to Python's, including where nobody depends on it:

2.2 Several implementations of the same thing

2.3 Untyped records

Records are Map<String, Value> navigated with get(..).and_then(..) chains and Python truthiness, not structs. y2p-contract has the schema names but few types. Typed serde structs for the records (power.json, media-mount.json, sd-write.json, download.json and the update journal, boot.json, network/dhcp/dns/wifi records, suspend receipts, report state) would remove most get/truthy code and make field typos compile errors.

2.4 Structure, errors, idioms

2.5 Efficiency (measure first; only where the player gains)

2.6 Leftovers of the Python era (small, do first)

3. What must not change (the contract)

Before changing any output, check CONTRACT.md "Callers". Fixed points:

Free to change (nobody depends on it, verify per case): JSON key order and spacing on stdout where only Reborn reads it as JSON, Python float repr where no reader compares text, argparse corner cases (abbreviations, "maybe you meant", exact error wording) beyond exit code 2, fake tracebacks (become refusal records), PyRandom streams. Each such change shows up as a golden diff: review and re-record it in the same commit.

4. Safety net and how to use it

5. Phases (each its own commits; never behaviour and structure in one commit)

  1. Leftovers (§2.6) and an unwrap/expect/unsafe audit list (no changes yet).
  2. One core in y2p-core: Mount/mountinfo, boot_id, atomic records (with mode and durability options), the

    lock API (exclusive, try, within-deadline, shared), the verified data volume, safe wrappers for the libc families. Replace the copies group by group; goldens unchanged.

  3. One JSON model: serde_json with preserve_order (vendor indexmap) or Ordered everywhere; delete

    power_json::Json, wifi::Json, pyjson::Py where the order/float behaviour is not contract.

  4. Typed records in y2p-contract (shared with Reborn where Reborn reads them; re-stamp with

    release.py sync-contract, commit Reborn with the identity env vars).

  5. Context and injection: one Context { root, clock, runner, system } passed down; remove the #[cfg(test)]

    seams and env-var plumbing; tests construct a context.

  6. Python emulation out where §3 allows: argparse → a small declarative parser keeping exit code 2 and the

    argument forms (owner decides on help text); Crash paths → refusal records (each a behaviour change: list them, owner reviews the list); py.rs/os_text/pyre/pyops reduced to what the contract needs.

  7. Split the long modules along their responsibilities (update: source/download, staging, journal, apply;

    media: lifecycle, check, window, repair, format; snapshot: per section).

  8. Efficiency (§2.5): measure on the player (plan §7 method in PLATFORM-RUST-PORT-PLAN.md: idle sampler 600 s,

    per-daemon CPU/RSS), then event-driven waits; a test build and an overnight drain to prove no regression.

  9. Docs: CONTRACT.md as the Rust contract (drop "as Python" wording), comments say why, not "as Python did".

Each phase: entry note in roadmap-gap-audit.md when it reaches a test build; numbers before/after on the player.

6. Test quality follow-ups (from the 2026-10-29 test port)

The Python unit tests were ported by eight agents; where Rust had no hook for what Python patched they adapted:

Phase 5 (injection) should turn most of these back into direct tests.

7. Owner decisions to ask for (when the phase comes)

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