v0.9.2 — 24-agent hardening (mini-nodes-are-routers)
Hardening pass landing the "mini-nodes are routers" architecture cleanly. Web/browser surfaces removed; librats frozen at v0.2.0; mini-node + full-node + player all tightened against the failure modes seen in the prior week.
Cellular wedge fix (three layers)
- Android
REQUEST_IGNORE_BATTERY_OPTIMIZATIONSone-shot prompt so Doze stops throttling the Dart isolate - Discovery refresh dropped 60s → 20s + paired bidirectional
mini.pingto fill the gap between librats's 15s TCP keepalive and the application-layer heartbeat (carrier NAT was silently closing idle mappings in that window) - Watchdog evicts stale
_miniNodePeerIdsand_relayViawhenvalidatedPeerIds.isEmptyso the next refresh repopulates from scratch instead of routing to ghost peer_ids
Mini-node (router) hardening
- Routes table now has a 10-min TTL + 30s reaper thread; was: routes lingered forever after the publishing full node disappeared
- 'F'-tag binary relay token-bucket rate-limit (50 MB bucket, 10 MB/s refill per peer)
- Dead-route eviction on
relay.forwardsend-failure +{status:dead_route}reply so the player re-picks a different full node immediately RatsLinkwatchdog tick 3s → 1s; route republish on every validated-peer count increase (not just first-up); eager 127.0.0.1 bootstrap in addition to the colocated-VPS loopback fallback (librats blocks dialing own public IP)
Full node (server/tracker) hardening
SwarmIndex::evict_peerwired to disconnect callback so online entries no longer linger 20 min after a silent socket deathrats_announce_for_hash(sha1(content_hash))fires on everyfingerprint.submit— DHT-based content discovery is now live on the seeder sideRelayCreditTrackerpre-capscountat 1M per tx (chain ceiling) with carry-over of the overflow into the next sweep
Player hardening
bestMiniNodePeerIdpicks lowestload_score(was: arbitraryvalidatedPeerIds.first)- Direct-when-reachable carve-out: desktop player whose own routes.get echo shows
reachability=='direct'tries direct librats dial before falling back to relay findContentSeeders(contentHash)adapter delegates tofindHashHolders(sha1(...))for DHT content lookupstream.opendisk-streamed (was:file.readAsBytesloaded whole song into RAM); 4-chunk pacing; cancel API wired to peer disconnect- Disconnect now triggers
cancelStreamsForPeerso the player stops blasting bytes at a dead receiver
Removed
musicchain_web/entirely (browser/web player path abandoned)ws_mini_gateway,ws_audio_bridge,ws_tcp_relay,ws_bridge,audio_fetch_handler(browser-facing C++)- All browser-protocol docs under
musicchain/docs/ - Full-node
--ws-portflag and the port 9090 WebSocket bridge
Frozen
deps/librats/reset byte-for-byte to v0.2.0 (commit 246557c); future limitations are wrapped at the call site (rats_link.cpp,rats_client.dart), never inside librats
Docs + memory
- New canonical reference:
musicchain/ARCHITECTURE.md - Memory bank updated:
feedback-no-web-version,feedback-dont-modify-librats,feedback-load-aware-routing,feedback-no-stale-peer-routing,project-architecture-doc
APK
Bundled below: musicchain-player-v0.9.2-arm64-v8a.apk (87.5 MB). Install:
adb install -r musicchain-player-v0.9.2-arm64-v8a.apk
First launch will prompt for battery-optimization exemption (Doze).
Known limitations (also tracked in ARCHITECTURE.md §8)
- RELAY_REWARD still per-stream, not per-byte
- No application-layer ACKs on binary relay (backpressure is time-paced, not credit-based)
- Single VPS in current dev/test deployment; multi-VPS mesh ready in code but not exercised here