# Download

**Job:** acquire media over BitTorrent and keep wanted titles alive.

One-liner: *Get files onto disk; keep enough seeders that magnets don't die.*

## Scope

| In | Out |
|----|-----|
| Torrent search (TPB/Nyaa/1337x) | Library browse UI (Watch) |
| Add magnet → transmission on the always-on host | Playback transport (Watch) |
| Transfer dashboard | Metadata enrichment (Watch) |
| Custody floor, rolling baton, re-pin | Net data editions (facts, not video) |
| Zombie reaper | |
| `peers_for` meta-tracker | |
| Duty assignment (broadcast, f2f_relay, swarm face) | |
| Watch → auto-seed with TTL | |
| Governor prefetch buffer (rsync next episodes) | |

Download is **availability**, not *arr-style acquisition automation*. The app
exposes mechanics; the governor enforces retention.

## User surfaces

### macOS

| Tab | Controller | Role |
|-----|------------|------|
| Search | `SearchController` | Find torrents, add to transmission |
| Downloads | `DownloadsController` | Active/completed transfers |

Completed torrents feed `LibraryScanner` (Watch) when folders land on the always-on storage host.

### iOS

| Tab | Role |
|-----|------|
| Downloads | `DownloadManager` — bridge-fetched offline files |

## Core modules (Swift)

| Module | Path | Role |
|--------|------|------|
| `TorrentService` | `Torrents/` | Shell to `mcp` for search + RPC |
| `DownloadsController` | `Torrents/` | Adaptive transmission dashboard |
| `TorrentModels` | `Torrents/` | Decode transfer state |
| `BandwidthPolicy` | root | Upload caps (friends vs public) |

`DeviceConfig.services` flags Download duties on each device:
`ttlSeed`, `custody`, `publicSwarmFace`, `f2fRelay`, `meshAnchor` (full list + defaults in [pillars/devices.md](./devices.md#device-services)).

## Governor (`governor/` — `portable-net-tv`)

Runs as launchd daemon on client machines. Independent of the app UI.

### Watch-adjacent (feeds Download)

| Module | CLI | Role |
|--------|-----|------|
| `watch.ts` | `portable-net-tv watch` | Follow VLC, append watchlog |
| `watchlog.ts` | — | Shared JSONL writer |
| `keeper.ts` | — | Prefetch next N episodes (rsync buffer) |
| `rsync.ts` | — | Buffer copy transport |

### Fleet engine (`governor/src/fleet/`)

| Module | CLI | Role |
|--------|-----|------|
| `registry.ts` | `fleet status` | Ingest `fleet.json` / `devices.json` |
| `duties.ts` | `fleet duties` | Assign broadcast / relay / swarm face / custody |
| `custody.ts` | `fleet custody` | Floor-check, `custodians_of`, re-pin **plans** |
| `actuate.ts` | `fleet repin --apply` | Execute re-pin (rsync holder → target) |
| `reaper.ts` | `fleet reaper [--apply]` | healthy / stalled / dead |
| `peers.ts` | `fleet peers`, `fleet serve` | Source model + `peers_for` HTTP |
| `transmission.ts` | — | SSH-tunneled JSON-RPC to the always-on host |
| `probe.ts` | `fleet probe` | Disk + EWMA uptime → custody eligibility |
| `daemon.ts` | `fleet daemon` | Periodic duties → floor → reaper tick |
| `serve.ts` | `fleet serve` | HTTP: `/registry`, `/custody`, `/reaper`, `/peers_for/<hash>` |

Placeholder docs only: [`fleet/README.md`](../../fleet/README.md) (WG fabric,
friend-mesh stage 4, Discord — not in repo code yet).

## MCP (`mcp/` — `tv-anarchy-mcp`)

App shells CLI; MCP clients use stdio server.

| Domain | Path | Role |
|--------|------|------|
| Transmission | `transmission/` | Search, add, list, RPC |
| Torrent search | `transmission/search.ts` | FlareSolverr-backed search |
| Bridge torrents | `bridge/torrents.ts` | iOS bridge download API |
| Fleet bridge | `bridge/fleet.ts` | Fleet status over HTTP |
| Media watchlog | `media/watchlog.ts` | Read/write shared JSONL |

## Search vendored fork

[`search/`](../../search/) — torrent index scrapers (TPB/Nyaa/1337x). Used by
`mcp` search path.

## Peer sources (data model)

From `governor/src/fleet/peers.ts` and `docs/data-model.md`:

| Source kind | Role |
|-------------|------|
| `dht` | Public DHT |
| `public_tracker` | Public trackers |
| `private_tracker` | Private tracker (forced `f2f_only`) |
| `friend_mesh` | F2F byte relay (stage 4) |
| `fleet_host` | Always-on fleet node |
| `seedbox` | Off-home swarm face |

Policy gates: `share_policy` (search_only vs content),
`swarm_isolation` (f2f_only vs open), passkey protection on privates.

## State (Download-primary)

| Artifact | Path | Writer |
|----------|------|--------|
| Fleet registry | `~/.config/tv-anarchy/fleet.json` | App / operator |
| Fleet engine state | `~/.local/state/tv-anarchy/fleet-state.json` | Governor |
| Governor config | `~/.config/portable-net-tv/config.json` | Operator |
| Watch log | `~/.local/state/tv-anarchy-mcp/watched.jsonl` | Governor (append) |
| Prefetch buffer dir | `config.json` → `buffer.dir` | Governor keeper |
| Transmission state | transmission daemon on the always-on host | transmission |

## Net enhancement (future)

Download **consumes** Net parts:

| Net part | Download consumer |
|----------|-------------------|
| `quality` | Pick living release before adding dead magnet |
| `peers-hints` | Seed `peers_for` when DHT is empty |
| `signal` (k-anon) | Reaper priority for dead-but-watched titles |

Download **produces** custody/holdings state that may surface as anonymized
Net hints (design TBD).

## Status

| Area | Status |
|------|--------|
| Search + transmission + Downloads UI | Shipped |
| Single-fleet custody / reaper / peers_for | Shipped (governor) |
| Re-pin actuation, probe, daemon, serve | Shipped |
| Friend-mesh byte relay (stage 4) | Designed |
| WireGuard fleet fabric | Designed, blocked |
| Seedbox / off-home face | Blocked (needs host) |

## Tests

| Location | Focus |
|----------|-------|
| `Tests/.../TorrentDecoderTests` | Torrent decode |
| `Tests/.../TransferHealthTests` | Transfer health |
| `governor/` (`bun test`) | Fleet engine |