tv-anarchy/docs/roadmap.md
Natalie ca1871f5dd feat(@applications/tv-anarchy): add roku device support
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 21:37:34 -07:00

12 KiB
Raw Blame History

Status & Roadmap

Where the project is and the de-risked path to the full vision. Architecture is in architecture.md; the mesh design origin is ../.project/history/20260608_fleet-manager-mesh-design.md.

Status at a glance

Area Status Note
App — playback (VLC / mpv / QuickTime) Shipped blacktv retired → mpv; no NFS — local players play downloads, else route to black
App — library browser Shipped black index fast-path + local MEDIA_ROOTS walk + registry fallback (no NFS)
App — downloads (search + transmission) Shipped via mcp CLI; search needs FlareSolverr
App — metadata enrichment + artwork Shipped regex parse + TMDB/IMDb/keyless + ffmpeg frame-grab
App — all UI (Home/Player/Library/Search/Downloads/Metadata/Adult/Devices/Logs/Settings) Shipped wired, no TODO/FIXME/fatalError debt
App — device registry (Devices tab) Shipped (registry only) DeviceConfig: type→services presets mapping to fleet classes, per-device load badge; duty engine still unbuilt (see fleet below)
App — adult content tab Shipped porn-rotation.py ported to PornCollectionService; ENABLE_ADULT compile flag + runtime pornFeature setting, concealed by default
App — VPN subsystem Shipped OVPN profile/credential stores (Keychain), controller, settings UI
App — offline cache + Now Playing/media keys Shipped OfflineCacheController, NowPlayingController, bandwidth policy
iOS app (TVAnarchyiOS) Shipped (companion) VLCKit player, library, downloads, remote control via HTTP bridge (default :8787); the bridge server is not in this repo's mcp/ tree
Distribution (release/update) Shipped tools/release.sh → Forgejo on forge.black; tools/update.sh installs/updates any node without a toolchain
governor (portable-net-tv) Shipped (single-host) watch tracking + prefetch buffer
mcp (plum-control-mcp) Shipped VLC / black-tv / transmission / display tools
recommender Shipped enrichment + local recs
MLX title refiner Shipped LocalLLMTitleRefinermedia_rec/title_refiner.py (MLX Qwen); cached, self-disabling, wired at app startup
governor → fleet orchestrator (stage 1) Shipped governor/src/fleet/: registry ingest, duty assignment, custody floor-check, zombie reaper, re-pin actuation (fleet repin --apply: rsync holder→target + recorded holdings), capacity probes (fleet probe: ssh df + EWMA uptime), periodic daemon (fleet daemon), research feed into mcp search
peers_for / custodians_of (stage 3 core) Shipped (single-fleet) source model + both policy gates enforced; unions fleet seedbox live DHT, provenance-tagged; served over HTTP by fleet serve (/peers_for/<hash>, bearer-token) — runnable on any node until a real broadcast host exists
Fleet WireGuard fabric (plane 1) Designed blocked on the 10.9.0.4 open question + root on each node
Seedbox source + off-home face Blocked external engine supports the class/duty today; needs an actual provisioned seedbox
Friend-mesh / F2F relay (stage 4) Designed needs other fleets to exist
Private-tracker source (stage 5) Designed gates already enforced in peers.ts; ships last, after F2F is battle-tested
Discord planes Designed control/QA/announce + availability bot; needs bot tokens/servers
Multi-identity / cross-fleet Designed single-fleet foundation must land first

Verdict: the media-client layer is complete and production-grade. The fleet/mesh layer now has its single-fleet core (registry → duties → custody → reaper → peers_for) implemented and tested in the governor; what remains is actuation (cross-host copies), the WG fabric, and every stage that requires infrastructure outside this repo (seedbox, friends, Discord).

Repo-state note

The PlumTV → TVAnarchy rename and the helper subsystems (governor/, mcp/, recommender/, search/, fleet/, tools/) were committed to main on 2026-06-09 as a series of atomic commits (41afc1cb44b5a2). Stage 0 (hygiene) below is done.

Remaining work — detail

MLX title refiner done 2026-06-09

LocalLLMTitleRefiner (Swift) shells into media_rec/title_refiner.py (MLX Qwen 1.5B, same model as the show grouper), consulted only for degenerate (<2-char) regex titles. Results are disk-cached (~/.local/state/tv-anarchy/title-refinements.json); two consecutive subprocess failures disable it for the session so a scan never pays repeated timeouts when MLX is absent. Wired at app startup. (The seam itself had a wiring bug — the raw-name fallback ran before the refiner check, making it unreachable — fixed in the same change.)

governor → fleet orchestrator — engine + read-only actuation done 2026-06-09

governor/src/fleet/ implements stage 1: registry ingest (the app-side fleet.json array is authoritative, devices.json the fallback), deterministic duty assignment with the spec's invariants, the N-copy custody floor-check with rolling-baton custodianship + re-pin planning, and the zombie reaper (healthy | stalled | dead, mesh-first recovery, public re-search fallback). CLI: portable-net-tv fleet status|duties|custody|repin|reaper|peers|probe|daemon|serve (all --json). reaper --apply performs only safe idempotent transmission nudges (reannounce/verify).

Completed 2026-06-09 (later, parallel agent team): the actuation layer — fleet repin --apply executes re-pin plans (rsync on the target pulling from the holder, recorded holdings feed back into the floor-check), fleet probe measures disk/uptime over ssh (EWMA score; feeds custody disk-eligibility), fleet daemon runs the duties→floor→reaper tick periodically for launchd, and fleet serve exposes /registry /custody /reaper /peers_for/<hash> over HTTP with optional bearer auth (the broadcast-host service, runnable on any node). Research actions build real mcp search invocations (fleet repin/reaper plans name them; execution behind apply flags). Remaining inside stage 1: nothing — what's left is infrastructure (targets need ssh + mediaRoot in fleet.json for repin to have somewhere to copy to) and the launchd plist itself.

fleet WireGuard fabric (plane 1) — blocked on a decision

The spec promotes a scoped fleet WG mesh (tva0, fleet-subnet-only AllowedIPs, collision-probe) to a foundational stage, but its design is explicitly blocked on the open question of what 10.9.0.4 is today (general overlay vs ad-hoc tv-anarchy overlay), and bring-up needs root on every node. Decide, then build.

stages 2/4/5 + Discord — blocked on external infrastructure

The engine already models seedboxes (class, duty preference, source defaults) and enforces the private-tracker gates; what doesn't exist is the infrastructure: a provisioned seedbox, friends' fleets for F2F, private-tracker credentials, Discord bots/servers. None of this is code in this repo until those exist.

Roku dev channel — planned milestone

The living-room display has a Roku Streaming Stick 4K (10.0.0.233, ECP) alongside black's HDMI input. ECP transport control is done (HostKind .roku / RokuTarget — pause/jump-back/exit/now-playing from the Devices tab). The channel itself — a couch-native TVAnarchy UI on the Roku — needs:

  1. HTTP media plane on black — Roku only plays HTTP(S) streams; today the media is NFS/ssh. A file server plus on-demand ffmpeg remux/transcode→HLS for the incompatible tail (mkv with FLAC/Opus audio, PGS subs). This is the heavy piece and is useful beyond Roku (any future web/TV client).
  2. BrightScript/SceneGraph client — library browse (black index over HTTP)
    • Video node playback. New codebase, no Swift reuse.
  3. Release pipelineTVAnarchy-<tag>-roku.zip asset + a push-deploy step (dev channels are uploaded TO the stick: curl --digest -u rokudev:<pass> -F archive=@channel.zip http://10.0.0.233/plugin_install). One dev-channel slot, persists indefinitely; dev mode enable = ECP keypress sequence.

Store publishing is out: Roku killed non-certified channels (2024) and certification would reject the torrent surfaces regardless of ENABLE_ADULT. Sideload-only, which the forge model already fits.

Build order (de-risked; each stage independently shippable)

From the design spec. Each stage ships on its own; later stages depend on earlier.

  1. Hygiene (prerequisite) done 2026-06-09: the PlumTV → TVAnarchy rename and the helper subsystems are committed on main.
  2. Host registry + duty assignment, single fleet done 2026-06-09 (engine): app-side registry (Devices tab, fleet.json) + governor-side duty engine, custody floor-check, and reaper (governor/src/fleet/). Remaining inside stage 1: re-pin actuation (cross-host copy execution) and running fleet duties/reaper under launchd instead of on demand.
  3. Seedbox source + public_swarm_face duty. Adds an always-on custodian; proves the source model on the zero-risk source first. ("Add a seedbox" is the single most important onboarding step for non-power-users — most multi-device fleets have zero always-on nodes otherwise.)
  4. peers_for() over local sources core done 2026-06-09: fleet peers <infohash|title> unions fleet holders seedbox live DHT peers, provenance-tagged, with both private-tracker policy gates enforced (search_only default-closed; f2f_only forced un-overridably). Remaining: a broadcast host to serve peers_for to other devices (today it's a local CLI query).
  5. Friend-mesh source + F2F relay. Federates; the custody floor goes cross-fleet. Multi-identity lands here.
  6. Private-tracker source, default-closed. LAST and highest-consequence: swarm_isolation forced to f2f_only, un-overridable; content share is an account-killer for the source user. Ship only once F2F isolation is battle-tested on safe sources.

Parallel, any time: MLX TitleRefiner done 2026-06-09. After a working mesh: the Discord planes — control/ops + QA/community + release announcements (project home server) and content/availability (private mesh

  • a bot inside users' own servers, never the public home server). Discord is a surface you notify, not a backbone you route through: custody/reaper/F2F run over WireGuard regardless, and a Discord ban must not take the mesh down.

Non-goals / explicitly deferred (not defects)

  • BlackTVTarget removed. Intentional — auto-migrated at runtime to MpvTarget with CommandsConfig.blackTVDefaults(bin:). Black-TV control still works.
  • No ratio enforcement. Resolved decision (2026-06-08): ship gift-economy, build the custody_capacity knob, leave it off. First lever is visibility, not enforcement; hard ratio is a per-group last resort. Ratio anxiety is the pain being sold against — "minus the ratio police."
  • No fingerprint-stripping on private shares. Resolved decision: warn hard, strip never by default. Stripping re-encodes and destroys the quality that makes a private release valuable; the friend is protected structurally by f2f_only, the source by the explicit warning. Strip is a per-release opt-in only, never auto.
  • App doesn't link governor or call the MCP server directly. By design — it shells out to the mcp CLI and recommender as subprocesses; governor runs independently under launchd.
  • Black-TV watch state not on plum. Intentional — black-local, read over SSH, never written across NFS.
  • Watch parties (if built) = synchronized local playback; Discord carries only voice + timestamps, never streamed copyrighted video.