tv-anarchy/docs/roadmap.md
Natalie 0a4cde36d1 feat(devices): add dependency issue warnings
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-09 21:57:08 -07:00

268 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Status & Roadmap
Where the project is and the de-risked path to the full vision. Architecture is in
[architecture.md](./architecture.md); the mesh design origin is
[`../.project/history/20260608_fleet-manager-mesh-design.md`](../.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 (macOS) / 🟡 scaffolded (rest) | `tools/release.sh` → Forgejo; `tools/update.sh` resolves per-OS asset+dest (mac/linux/windows/android/iOS) via the single-source `tools/platform.sh`. Only the macOS asset can exist until the client is cross-platform (see north star) |
| Universal client (one app, all devices) | ❌ Designed | the spine: `blackd` → thin web client → wrap (Tauri/Capacitor) → retire Swift UIs; adult becomes a backend-entitled module. See "North star" |
| `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 | `LocalLLMTitleRefiner``media_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).
## North star — one client, every device
The goal: **one app we install on every device** — iOS, Android, macOS, Ubuntu,
Bluefin, Windows — controlling the display endpoints (Roku, smart TVs) it can't
run on, **with the adult feature as an opt-in package on entitled devices only**.
**Why today's app can't be that.** It is Swift + SwiftUI/AppKit/UIKit + VLCKit —
structurally Apple-only (29 UI files; the core leans on Observation/AppKit/
MediaPlayer). It reaches only 2 of the 6 install targets. The per-platform
branches in `tools/update.sh` (linux/windows/android) are honest scaffolding for
release assets a Swift+SwiftUI build **cannot produce**. No amount of packaging
fixes that; the *client technology* is the constraint.
**The unlock is already on the roadmap.** ~20% of `TVAnarchyCore` exists only to
reach black over ssh/Process, and most of the rest *fronts* state that truly
lives on black (the index, the watchlog, transmission, porn-rotation). Once
`blackd` (below) exposes those as HTTP, the client keeps almost no logic — it
becomes **browse + control + a `<video>`**. A thin client is portable; a fat one
isn't. So the dependency order is: **server-side consolidation first, universal
client second.** They're one program, not two.
**Client decision (the one real fork).** To be genuinely *one* codebase across
all six:
- **Recommended — web/PWA, wrapped:** one TypeScript app; Tauri for
macOS/Windows/Linux installs, Capacitor for iOS/Android, raw PWA in any
browser (covers Bluefin and anything else for free). Talks only HTTP+WS to
`blackd`. Reuses the workspace's TS toolchain (governor/mcp). Roku/TVs are
*controlled*, not install sites. **Cost:** the Swift macOS/iOS UIs become
legacy and are retired at parity — a real rewrite, paid down by the logic
moving server-side where it belongs. **Tradeoff to weigh:** native VLCKit
offline playback is best-in-class today; a PWA's offline story (service worker
+ Capacitor filesystem on mobile) is good but not equal — this is the one
capability the pivot risks.
- **Pragmatic alternative:** keep Swift for Apple (where it's excellent), ship
the web client only for the non-Apple platforms. Two clients, not one —
satisfies *reach* but not the literal "one app" goal.
**Adult as a package (honoring "some include it").** In the web-client world the
adult feature becomes a **lazily-loaded module gated by a per-device entitlement
the backend issues** — clients are byte-identical everywhere; black serves the
adult module *and* adult library rows only to entitled devices. That's strictly
stronger than today's client compile flag (an un-entitled install never holds
adult data to leak). The existing `ENABLE_ADULT` compile-strip survives as a
**store-safe variant** for the one case that needs zero adult code present
(an app-store submission) — cut from the same source via the asset-suffix
dimension the release pipeline already supports.
**Sequence (each phase ships):** 1) `blackd` — HTTP/WS service plane on black
(player, index, **`/media`**, verbs); 2) thin **web client** against it
(browse + control + player) reaching parity with the Swift app feature-by-
feature; 3) **wrap** (Tauri/Capacitor) + entitlement-gated adult module;
4) **retire** the Swift UIs and the plum iOS bridge. The `/media` plane in (1)
is the same one the Roku channel needs — bought once.
> Open decision for the operator: commit to the web-thin-client rewrite (true
> one-app, accept the offline-playback tradeoff), or hold Swift-for-Apple +
> web-for-the-rest (reach without a single codebase). Everything before the
> client rewrite — `blackd`, the `/media` plane — is correct either way, so the
> fork can be deferred until phase 2 without stalling phase 1.
### 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 (`41afc1c``b44b5a2`). 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 dutiesfloorreaper 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.
### `blackd` — black as a real service (no ssh on the control path) — planned milestone
**This is phase 1 of the universal client** (see north star): it's the
prerequisite that thins the client enough to be portable, not merely an ssh
cleanup. ssh-as-transport is the app's load-bearing design debt: player control is a
fresh `ssh → sudo socat → /tmp/mpv.sock` pipeline per command (`MpvTarget`),
the library index is `ssh cat`, launch/stats/releases/restart are ssh-invoked
`black-tv` verbs (whose deploy drift required the `helper_sha` badge), and the
offline cache rsyncs over ssh. Consequences: every keyless client needs a proxy
(the iOS app exists only via the plum `:8787` bridge; a Roku/web client can't
ssh at all), every capable client reimplements ssh plumbing, and the `sudo
socat` hack exists purely because the far socket is root-owned. The
counter-examples are already in the stack: transmission (HTTP RPC :9091) and
the Roku's ECP are the robust integrations.
**Design:** one daemon on black HTTP on LAN + WG overlay, bearer token:
- `/player/*` + WebSocket events local unix-socket access to mpv (root
problem dissolves; no per-command process spawn)
- `/library/index` replaces `ssh cat`
- `/media/<path>` (HTTP range now, on-demand HLS remux later) the SAME media
plane the Roku channel / web client milestone needs; one investment
- `/launch`, `/stats`, `/releases`, `/restart` the `black-tv` verbs
- `/version` replaces helper_sha drift detection
**Migration (each phase shippable):** 1) daemon wraps the existing pieces
(systemd unit; deploy via the existing helper mechanism); 2) `BlackdTarget`
(PlayerTarget/MediaLaunchable over HTTP, like VLC's) preferred with ssh
fallback; 3) retire ssh paths + the plum iOS bridge (iOS talks to black
directly). ssh remains for deploys/admin only.
### 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/transcodeHLS
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 pipeline** `TVAnarchy-<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.
0. ~~**Hygiene (prerequisite)**~~ done 2026-06-09: the `PlumTV → TVAnarchy`
rename and the helper subsystems are committed on `main`.
1. ~~**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.
2. **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.)
3. ~~**`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).
4. **Friend-mesh source + F2F relay.** Federates; the custody floor goes
cross-fleet. Multi-identity lands here.
5. **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.