# Seedbox Command Center — Project Specification

> **Status:** Master architectural specification. Authoritative for all design decisions.
> **Audience:** The engineer (human or AI) implementing this system, and the operator deploying it.
> **Version:** v2

A unified, mobile-first command and observability system for private tracker seedbox operations, positioned as a meta-layer above the modern open-source seedbox ecosystem rather than a reimplementation of it.

---

## 1. Executive Summary

The Seedbox Command Center is a self-hosted progressive web application that aggregates control, observation, and notification for private tracker seedbox infrastructure into a single coherent interface. It is designed for installation on a mobile device home screen as the primary daily interface, while remaining accessible from any device on the operator's private network mesh.

This specification incorporates a strategic positioning driven by a survey of the current state of the seedbox tooling ecosystem. The autobrr team has shipped qui, a single-binary qBittorrent management UI with multi-instance support, rule-based automations, cross-seed orchestration, scheduled backups, and a transparent reverse proxy. The same team's tqm tool provides a declarative torrent queue manager with a rich expression language. Cross-seed itself now performs announce-matching that pairs with autobrr to enter swarms as a one-hundred-percent seeder the instant an upload is announced. Multiple mature Prometheus exporters and Grafana dashboards exist for qBittorrent metrics collection.

In light of this ecosystem, the Command Center positions itself as the intelligence and integration layer above qui, autobrr, tqm, cross-seed, and the arrs. It does the things none of those tools do individually: cross-system correlation of grabs to upload contribution, tracker-scrape intelligence with derived metrics, hit-and-run prediction, conversational decision support driven by a local language model, simulation of rule changes against historical data, health-budget tracking inspired by SLO engineering, and unified mobile-first notification across the whole stack.

The system is built for a single operator on trusted devices, with strong identity protection through WebAuthn passkeys, a strict no-public-exposure networking model using Tailscale, and a deployment story that aligns with the Linux-and-Docker conventions of the surrounding ecosystem.

---

## 2. Problem Statement

Modern private tracker operations require coordinating multiple loosely-integrated services. A typical setup involves a torrent client (qBittorrent or similar), an automation layer (autobrr operating on IRC announce channels and RSS feeds), one or more tracker websites with their own statistics and rules, optional arr-stack media management, cross-seed orchestration, and physical infrastructure (the seedbox itself, often a rented slot on a managed hosting provider).

Operators face several recurring friction points that no individual tool addresses. Real upload ratio, the metric that matters most for tracker progression, is rarely surfaced cleanly; most tools display only the ratio as reported by trackers, which is often inflated by tracker incentives like freeleech or bonus-point exchanges. Filter performance is difficult to measure because the connection between an automation grab and its eventual upload contribution is rarely surfaced anywhere. Hit-and-run policies vary by tracker and by torrent, and the consequences of accidental violation can include account suspension on trackers that took months or years to gain entry to. Mobile access to the entire stack is fragmented across a half-dozen service web interfaces, each with its own authentication and its own mobile-quality considerations. The cumulative cognitive load of running a serious private-tracker operation is substantial, and most of it consists of mentally correlating data that lives in five different places.

The Command Center addresses these problems by treating the full stack as a coherent system rather than a collection of independent services, and by providing the analytical and decision-support layer that none of the individual tools were ever designed to provide.

---

## 3. Strategic Positioning

The most important architectural decision in this specification is what the Command Center is *not*. It is not a torrent client UI. It is not a replacement for autobrr. It is not a cross-seeder. It is not an arr. Each of those problems has an excellent open-source solution in active development by skilled maintainers, and any attempt to compete with those solutions would be a poor use of engineering time.

Instead, the Command Center is the meta-layer that sits above all of them. It observes them, correlates their data, surfaces insights they cannot produce individually, and provides a single mobile-first interface for the operations that span multiple services. Where actions are required against an underlying service, the Command Center delegates to that service's API rather than reimplementing its logic. Where data is required from an underlying service, the Command Center reads from that service's API or webhook stream rather than maintaining a parallel state machine.

This positioning has three practical consequences. First, several phases of the implementation plan are dramatically shortened or eliminated relative to a build-everything approach, freeing engineering capacity for the differentiated features. Second, the integration adapters become first-class architectural concerns rather than afterthoughts, because the system's value depends entirely on the breadth and reliability of what it can observe. Third, the system's defensibility comes not from feature parity with any single tool but from the cross-cutting intelligence that no single tool can produce.

---

## 4. Goals and Non-Goals

The goals of the system can be stated in five points of emphasis. The system provides a single mobile-installable application that surfaces all operationally relevant data in one place. It computes derived metrics that no individual service provides, including real ratio velocity per torrent, predicted hit-and-run risk dates, and filter-to-upload contribution chains. It enables actions across the integrated services without requiring access to any individual service's interface. It provides intelligent push notifications driven by a configurable rules engine rather than the noisy default of "everything." It maintains rich historical data sufficient for both analytical queries and what-if simulation of proposed rule changes.

The non-goals are equally important to state plainly. The system is not a replacement for qui, autobrr, qBittorrent, cross-seed, tqm, Prowlarr, Sonarr, or Radarr; these continue to operate independently, and the Command Center reads from and writes to them rather than substituting for them. The system is not designed for multi-user operation, public exposure, or shared access, and its security model is calibrated for a single trusted operator on trusted devices within a private network mesh. The system does not attempt to circumvent tracker rules or rate limits; integrations strictly respect each tracker's published automation policies, and the scraping hygiene layer is designed to be a good citizen rather than to maximize throughput.

---

## 5. System Architecture

### 5.1 Network Topology

The system runs on a single always-on host. The recommended deployment is a small Linux machine (physical or virtual) running Docker, because the entire surrounding ecosystem assumes Linux conventions and shipping containerized services to it is materially simpler than wrapping Go binaries as Windows services. Operators who must run on Windows should use WSL2 with systemd as the bridge.

Inbound access from the operator's mobile device is provided exclusively through Tailscale, a WireGuard-based mesh VPN. No ports are exposed to the public internet. The backend service binds only to the Tailscale virtual network interface, refusing connections from any other source.

```
                    ┌──────────────────────────────────────┐
                    │  Mobile Device (Tailscale node)      │
                    │  PWA: cc.<tailnet>.ts.net            │
                    └────────────────┬─────────────────────┘
                                     │
                              Tailscale (WireGuard)
                                     │
              ┌──────────────────────┴───────────────────────┐
              │  Linux Host (Tailscale node)                 │
              │                                              │
              │  ┌─────────────────────────────────────────┐ │
              │  │  Command Center (single Go binary)      │ │
              │  │  - Embeds frontend bundle               │ │
              │  │  - Binds to Tailscale IF only           │ │
              │  │  - systemd unit, auto-restart on fail   │ │
              │  └────────┬────────────────────────────────┘ │
              │  ┌────────┴────────────────────────────────┐ │
              │  │  SQLite (state) + DuckDB (analytics)    │ │
              │  └─────────────────────────────────────────┘ │
              │  ┌─────────────────────────────────────────┐ │
              │  │  Local LLM (Ollama, optional)           │ │
              │  └─────────────────────────────────────────┘ │
              │  ┌─────────────────────────────────────────┐ │
              │  │  Adjacent services in the same network: │ │
              │  │  qui, autobrr, cross-seed, tqm, arrs    │ │
              │  │  + Byparr (Cloudflare bypass)           │ │
              │  └─────────────────────────────────────────┘ │
              └──────────────────────┬───────────────────────┘
                                     │
                          Outbound internet
                                     │
              ┌──────────────────────┼──────────────────────┐
              ▼                      ▼                      ▼
         Tracker sites          Push services        Off-host backup
         (HTML scrape           (web push)           (encrypted, R2/B2)
          + Byparr)
```

### 5.2 Backend Stack

The backend is implemented in Go. The entire surrounding ecosystem the Command Center integrates with — autobrr, qui, tqm, the Prowlarr-and-arr family — is written in Go and ships as single static binaries. Choosing Go aligns the deployment story (one binary plus a config file, drop in and run under systemd), the dependency story (no node_modules tree, no version-pinning gymnastics), and the operational story (low memory footprint, deterministic startup) with the conventions of the surrounding tools.

The web framework is Echo or Chi (both are excellent for the API surface this system requires; the choice is largely taste). HTTP routing, middleware, and JSON serialization use standard library plus a few well-maintained dependencies. Background work is handled by a combination of cron-style schedulers for periodic tasks (tracker scrapes, snapshot collection, rule evaluation) and an internal event bus for reactive work (webhook ingress, real-time notifications). The internal event bus is implemented as a typed pub/sub structure over Go channels, which gives in-process composition without the operational overhead of an external message broker.

The frontend assets are embedded into the Go binary using `embed`, so deployment is genuinely a single file. There is no separate web server, no reverse proxy required, no static asset handling to configure. The binary serves the PWA at the root path, the API under `/api`, and the WebSocket and Server-Sent Events endpoints under their respective paths.

### 5.3 Frontend Stack

The frontend is a React 18 application built with Vite and written in TypeScript, styled with Tailwind CSS. Server state synchronization uses TanStack Query. Small amounts of UI-local state use Zustand. The component library is shadcn/ui, which gives a consistent baseline of accessible components that can be customized without lock-in to a particular design system.

Progressive web app capabilities come from vite-plugin-pwa, which generates the manifest and service worker required for iOS home screen installation. The PWA is configured for standalone display mode with appropriate viewport, theme color, and safe-area handling for modern mobile devices including those with display notches or dynamic islands. iOS 16.4 or later is required for web push support inside installed PWAs.

Real-time updates from the backend reach the frontend through a hybrid of WebSocket for bidirectional channels (operator-initiated actions that need immediate confirmation) and Server-Sent Events for one-way data flow (snapshot updates, event-bus broadcasts). The frontend subscribes to event topics relevant to the currently visible page and uses incoming events to invalidate TanStack Query caches, causing affected views to refresh without a full page reload.

### 5.4 Time-Series Storage Strategy

The storage strategy uses two complementary stores. SQLite holds transactional state (configuration, secrets, rule definitions, current torrent metadata, audit log) where row-level reads and writes dominate. DuckDB is added in-process for analytical queries against snapshot data, using its columnar storage and vectorized execution to make the intelligence features fast even against very large historical windows. The two databases live in the same process and the snapshot collectors write to both, so there is no synchronization concern.

For operators who already run a Prometheus-and-Grafana observability stack, the Command Center can optionally publish all snapshot data as Prometheus metrics in addition to its internal storage. This enables the operator to use Grafana for ad-hoc dashboards while keeping the Command Center as the primary interactive interface.

### 5.5 Event Bus and Integration Layer

The system is event-driven first and poll-driven only as a fallback. Webhooks and external events are the primary data source.

The integration layer subscribes to four classes of event source. The first is autobrr's webhook output, which fires on every filter match and provides low-latency awareness of grabs. The second is qBittorrent's external-program-on-event hook, which fires on torrent completion, error, and tracker-status changes. The third is cross-seed's webhook output, which fires when matches are found or applied. The fourth is the Command Center's own webhook receiver endpoints, which any custom script or external monitoring tool can publish to with an HMAC-validated token.

Events from these sources are normalized into an internal event schema and published onto the in-process event bus. Subscribers within the Command Center (the rule engine, the snapshot collector, the notification dispatcher, the audit logger) handle them according to their own concerns. Cron-driven scrapers and pollers remain in place, but their role shifts from primary data source to reconciliation: they catch up state that the event stream missed, and they collect data that no upstream system publishes (tracker-side ratio statistics, in particular, which still require periodic HTML scraping).

### 5.6 Authentication

Authentication is implemented using WebAuthn passkeys. The operator registers credentials on first use from a desktop browser, and the registered credentials may then be used from any device that supports them, with iOS providing biometric unlock through Face ID or Touch ID. No passwords are involved after initial registration.

Recovery codes are generated at first registration and stored by the operator out-of-band (a password manager is the natural choice). In the event of credential loss, recovery codes permit re-enrollment of new credentials through a dedicated recovery endpoint that does not require an active session but does enforce strict rate limiting and full audit logging.

Session tokens issued after successful authentication are stored as HTTP-only secure cookies with the SameSite-Strict attribute and validated on every request. Session lifetime is configurable; the default is thirty days with automatic renewal on activity.

### 5.7 Secrets Management

The system uses age, the modern file-encryption tool, to encrypt secrets at rest. A single age identity is generated at first run and stored in the platform's native keychain: the system keyring service on Linux (which transparently uses GNOME Keyring, KWallet, or libsecret depending on the desktop environment), macOS Keychain, or Windows Credential Manager. Headless Linux deployments use a TPM-sealed key or a file-system-restricted key in `/etc/command-center/age.key` with strict permissions.

Secrets are stored as age-encrypted blobs in the SQLite secrets table, decrypted in memory on demand, and never logged or persisted in plaintext. The encryption is keyed to a single identity rather than per-secret, which keeps the code simple and aligns with the threat model (a single operator's host machine, not a multi-tenant secret-store).

A secondary benefit of using age is that the same identity can encrypt the configuration files and backup archives, providing a unified cryptographic story across the system. Operators can also use SOPS (which builds on age) to manage encrypted configuration files declaratively, which fits cleanly into the configuration-as-code approach described later.

### 5.8 Logging and Self-Observability

All log output flows through a structured logger (zerolog or zap in Go) configured with explicit redaction rules. Field names matching any of `password`, `cookie`, `token`, `api_key`, `secret`, `authorization`, `set-cookie`, `raw_cookie`, or `mam_id` are automatically replaced with `[REDACTED]` regardless of log level. This addresses a common operational failure mode in which debug-level logging captures credentials in plaintext, where they may be inadvertently exposed during troubleshooting or support interactions.

Beyond redaction, the Command Center treats observability of itself as a first-class concern. The binary exposes a Prometheus metrics endpoint covering request rates, scrape success and failure counts, integration adapter health, event bus throughput, and database query latencies. Structured logs are emitted in a format compatible with Loki, Promtail, or Vector for log aggregation. OpenTelemetry traces are produced for the integration adapter paths, which is where most operational issues manifest, and these can be exported to Jaeger, Tempo, or any OTLP-compatible collector.

Operators who do not want a full observability stack get reasonable defaults out of the box: a built-in `/api/system/health` endpoint covers liveness and readiness, a `/api/system/events` endpoint exposes the internal system event log with filtering, and the dashboard surfaces integration health prominently.

### 5.9 Configuration as Code

Configuration is split into runtime state (what is currently happening, modifiable through the UI) and declarative configuration (filters, rules, tracker definitions, notification rules, automation rules — modifiable through git-versioned YAML files).

The declarative configuration directory contains files like `filters.yaml`, `notification-rules.yaml`, `tracker-rules.yaml`, and `automation-rules.yaml`. The Command Center watches this directory and reloads changes automatically, validating each change against a schema before applying it. A change that fails validation is rejected and logged; the previous configuration remains in effect. The UI can read and display the current configuration but writes only through the API endpoints that update the underlying YAML files, which the operator may then commit to a private git repository for version control.

---

## 6. Data Model

The database schema is designed to support transactional state queries, time-series analytics, decision provenance, and what-if simulation. The full schema is provided in Appendix A.

Notable concerns covered by the schema include: secrets and WebAuthn credentials, push subscriptions, trackers and torrent clients and automation tools, ratio snapshots and torrent snapshots (with simulation_id columns to support what-if runs without polluting real data), torrents and tracker associations, filter performance correlation, notification rules and delivery log, decision provenance with full causal chains, simulation run metadata, health budget state tracking, tracker rule corpus (loaded from YAML, cached for query), optional LLM conversation history, audit log, system events, webhook endpoints, and schema migrations.

---

## 7. External Integrations

### 7.1 qBittorrent and qui

For torrent client integration, the Command Center supports two modes. In direct mode, it talks to qBittorrent's standard REST API using cookie-based authentication and the efficient sync endpoint that returns diffs rather than full state. In proxied mode, it talks to qui's reverse-proxy endpoint, which qui exposes to give external apps a stable interface even when the underlying qBittorrent moves or its credentials rotate. Both modes are supported through the same internal adapter interface, and operators choose based on whether they run qui or not.

The torrent client adapter interface is also implemented for rTorrent, Transmission, and Deluge, but with the strategic note that the Command Center is not trying to provide a great UI for any of these — operators who use them are expected to use their native or community UIs for direct manipulation and to use the Command Center primarily for cross-system observation.

### 7.2 autobrr

The autobrr integration is two-way. The Command Center polls autobrr's REST API for filter definitions, recent release history, and announce-source connection status. It also subscribes to autobrr's webhook output, which fires on every filter match and provides a low-latency event stream. The webhook subscription is configured through autobrr's own configuration; the Command Center provides a known endpoint and an HMAC-validated token.

Filter performance correlation is the most valuable derived metric this integration produces. Each grab webhook is recorded with a stable identifier, then the Command Center tracks the resulting torrent's upload contribution over time and updates the filter's performance score. Over weeks, this produces a ranked view of which filters are actually generating real upload rather than which filters happen to grab the most things.

### 7.3 cross-seed

Cross-seed runs as a daemon (typically in Docker) and emits webhooks on match found and match applied. The Command Center subscribes to these webhooks to update its filter-performance correlation (cross-seed-injected torrents count their upload toward the source filter's performance score) and to surface cross-seed activity in the dashboard.

The Command Center also presents the operator with a cross-seed activity view that shows recent matches by source tracker and destination tracker, the current backlog of unmatched torrents, and the per-tracker hit rate.

### 7.4 tqm

For automated cleanup, the Command Center delegates to tqm rather than implementing rule evaluation itself. The operator's tqm filter definitions live in the declarative configuration directory, the Command Center watches and validates them, and tqm runs on its own schedule under systemd. The Command Center records each tqm run's outputs (which torrents were removed, tagged, or updated) and surfaces them in the audit log and recent-activity views.

This is a deliberate division of responsibilities. tqm has a mature, well-tested rule evaluation engine; reimplementing it would be a poor use of time. The Command Center's value-add is the what-if simulation layer above tqm's rules, the health-budget tracking that catches rules running too aggressively, and the decision provenance that makes every cleanup auditable.

### 7.5 Tracker Statistics

Most private trackers do not expose a public API for statistics. The Command Center scrapes the operator's profile page on each tracker, respecting each tracker's stated automation policy. For trackers behind Cloudflare, the system can fall through to a local Byparr instance, which runs a real browser behind a FastAPI server and bypasses anti-bot challenges to return valid clearance cookies.

The scraping layer is wrapped in a hygiene primitive that randomizes timing within operator-configured windows (the default is "scrape every 5 to 7 minutes" rather than "scrape every 5 minutes exactly"), honors `Retry-After` and rate-limit headers, persists cookies in the secrets store, and falls back gracefully when a tracker's HTML layout changes. A scrape failure is treated as a first-class event: it appears in the dashboard with a stale-data indicator, fires a notification rule if configured, and is recorded in the system event log for trend analysis.

### 7.6 Notification Delivery

Push notifications are delivered to subscribed devices via the Web Push Protocol using VAPID-signed messages. The system maintains push subscriptions per device, supports multiple devices simultaneously, and cleans up stale subscriptions after configurable failure thresholds.

For mobile-specific enhancements that the web platform cannot deliver — iOS Live Activities, Dynamic Island integration, Focus Mode awareness — the Command Center exposes the necessary APIs to support an optional thin Swift companion app. The companion app is out of scope for v1 but the API surface is designed to support it.

---

## 8. Feature Set

### 8.1 Core Observation Features

The dashboard view shows current state across all integrated systems in a single scroll-free layout optimized for mobile portrait orientation. Ratio cards per tracker display current real and displayed ratios with trend indicators. The active torrents summary shows total counts, current speeds, and disk usage. Recent automation activity shows the last several filter grabs with status. Any active notifications appear prominently. Integration health is visible at the top of the dashboard.

The torrents view delegates the deep torrent-manipulation UX to qui when present, but provides a cross-instance and cross-tracker-aware view that qui itself does not. The trackers view shows per-tracker statistics with historical graphs of ratio, upload, and download over selectable time ranges. The automation view surfaces autobrr filter state, cross-seed activity, and tqm rule results in one unified timeline.

### 8.2 Derived Metrics and Intelligence

Real ratio velocity is computed per torrent over rolling time windows, identifying torrents that are actively earning upload versus those that have stalled. Combined with size data, this produces an upload-per-disk-byte efficiency score that informs cleanup decisions.

Hit-and-run risk prediction tracks each torrent against its tracker's seed time requirements. The system knows when each torrent was added, how much seed time it has accumulated, and how much is required for the specific tracker and torrent size. Torrents approaching the risk threshold are surfaced before they become hit-and-run violations.

Filter performance correlation ties each automation grab to its downstream upload contribution. Filters are scored by their grabs' eventual real upload, not by grab count. Filters with high grab counts but low upload contribution are flagged as candidates for tightening; filters with low grab counts but high per-grab upload are flagged as candidates for relaxation.

Dead swarm detection identifies torrents that have been seeded for a configurable threshold (default seventy-two hours) with no upload activity. These appear in a dedicated review queue with hit-and-run risk safeguards preventing premature deletion.

Disk consumption forecasting projects disk usage forward based on recent grab rate and average torrent size, with Monte Carlo simulation to provide confidence intervals rather than point estimates.

Tracker token economy analysis tracks point accumulation rates, optimal exchange thresholds, and surfaces recommendations when accumulated points exceed configured thresholds.

### 8.3 Simulation and What-If Engine

Before the operator enables a new cleanup rule, a new notification rule, or a tightened autobrr filter, the system replays the proposed configuration against historical data from a configurable window (typically thirty to three hundred sixty-five days) and produces a deltas report. Which torrents would have been deleted under this rule? Which notifications would have fired, and when? What ratio impact would the change have produced? How would filter performance scores have shifted?

The simulation engine works by taking the proposed rule, the historical snapshot stream, and a fresh sandboxed evaluation context, then replaying time as if the rule had been active. Real state is untouched; the results are written to a separate simulation table with a stable simulation identifier for later inspection or comparison.

### 8.4 Decision Provenance

Every recommendation the system makes carries a fully expandable causal chain. The chain shows the data that led to the metric calculation, the rule or threshold that fired, the assumptions baked into the math, the timestamps of each input, and the alternative actions the system considered before settling on the recommended one. The operator can tap into any recommendation and trace it back to first principles.

This serves three purposes. It builds operator trust in automated recommendations. It provides a debugging tool when recommendations go wrong. And it produces an audit trail that survives indefinitely.

### 8.5 Conversational Layer with Local LLM

The Command Center optionally runs a small language model locally — Ollama running a 7-to-13-billion-parameter model — and exposes a conversational interface to it that is grounded in the operator's actual data through structured tool calls.

The operator can ask questions like "what is hurting my ratio this week," "which torrents should I delete to free 100 gigabytes," "did the new filter actually generate upload," or "summarize my seedbox health in one paragraph." The model receives the question along with a set of tool definitions that let it query the database, fetch current metrics, and inspect rule definitions. The model produces a response that is grounded in the actual data rather than in pattern-matched generalities.

Local-first deployment is the default. The Command Center never sends operator data to a remote LLM provider unless the operator explicitly enables remote inference and accepts the privacy trade-off, in which case requests are routed through a redaction layer that strips obvious identifying information before forwarding.

The conversational layer is also the foundation for the iOS Shortcuts integration. The Shortcut becomes a single voice gateway to the LLM: "Hey Siri, ask Command Center what's hurting my ratio."

### 8.6 Health Budget Framework

Inspired by SLO engineering practice from production-software operations, the operator sets monthly tolerances against specific failure modes. Examples include "up to one hit-and-run violation per quarter," "disk utilization above ninety percent for no more than two days per month," "ratio below zero point five for no more than four hours per week," and "tracker scrape failure for any single tracker for no more than thirty minutes per day."

The system tracks burn rate against each budget and surfaces warnings when consumption is too fast relative to time elapsed in the period.

### 8.7 Multi-Armed Bandit Filter Tuning

For operators who want to push filter optimization further, the system supports parallel filter variants with traffic splitting and learns which variant produces the most real upload per grabbed byte. The operator defines a base filter and one or more variants (perhaps tighter size limits, different group inclusions, different tracker subsets), and the system splits credit across them, recording the outcomes.

This needs careful design to respect tracker rules. The system only grabs what the most permissive variant would have grabbed anyway; the bandit chooses which variant to credit for each grab, not which variant to actually fire. The grab itself is unconditional once any variant matches.

### 8.8 Tracker Rule Corpus

Each private tracker has dozens of unwritten or scattered rules — minimum seed times by torrent size, hit-and-run policies, trump rules, group blacklists, internal-versus-scene preferences, account inactivity thresholds, freeleech detection sensitivity, parking allowances. Today these live in operators' heads, scattered wiki pages, or buried forum threads.

The Command Center introduces a structured tracker rule corpus: a YAML schema that captures these rules in machine-readable form, loaded from the declarative configuration directory at startup. The system can produce warnings before actions: "this torrent is a P2P release and Tracker X auto-removes non-internal duplicates within 24 hours of an internal release; consider waiting," or "this tracker requires 96 hours minimum seed time for torrents under 5 gigabytes; current seed time on this torrent is 47 hours, so deletion now would create a hit-and-run."

### 8.9 Scrape Hygiene

Tracker scraping is treated as a first-class concern with explicit primitives rather than as a side effect of "make the HTTP request." The hygiene layer randomizes scrape timing within operator-configured windows, honors `Retry-After` and rate-limit headers, persists cookies in the secrets store, falls back to a local Byparr instance for Cloudflare-protected sites, rotates user agents within a small operator-approved pool, and exponentially backs off on consecutive failures with jitter.

When a scrape fails repeatedly, the system distinguishes between three categories of failure. Network failure triggers retry with backoff but no operator notification until the configured threshold. Authentication failure triggers a high-priority notification because the operator needs to refresh credentials. Structural failure triggers a different notification because the tracker has probably changed their layout.

### 8.10 Panic Button and Emergency Controls

A single tap from the mobile dashboard activates emergency mode. In this mode the system pauses all torrents on all clients, stops all autobrr filters, disables all tqm automation, silences notifications for a configurable window (default two hours, extendable in the UI), and writes a prominent audit entry. The emergency mode is recoverable with a second tap, which restores prior state from the snapshot taken at emergency-mode activation.

### 8.11 Notification System

Notifications are driven by a rules engine rather than hard-coded events. Each rule specifies a trigger condition, a notification template, and delivery preferences including cooldown periods to prevent notification storms.

Built-in trigger types are documented in Appendix C and include ratio thresholds, ratio velocity changes, unsatisfied-torrent-count approaching tracker limits, hit-and-run risk approaching, automation tool disconnection, torrent client unreachable, filter grab activity, disk usage thresholds, completed torrent above size threshold, tracker scrape errors classified by type, and health budget burn-rate alerts.

Notifications are delivered through web push (the primary channel), the dashboard activity feed (always populated), and optionally through additional channels configured per rule: ntfy.sh, Discord webhooks, or email.

---

## 9. Phased Implementation Plan

The system is built in fifteen phases. Each phase produces a deployable artifact that delivers value independently. Operators may choose to stop at any phase boundary.

**Phase 0: Foundation.** Network setup, project scaffolding, systemd unit, empty backend bound to Tailscale, SQLite and DuckDB schema initialization, logging with redaction, configuration-as-code directory with validation. Estimated four to six hours.

**Phase 1: First Tracker Scrape, First View.** First tracker integration with scrape hygiene, periodic scrape job, ratio API endpoints, minimal frontend with installable PWA showing one tracker's ratio and history graph. Estimated six to eight hours.

**Phase 2: Authentication.** WebAuthn registration and login flows, session management, auth middleware, mobile login screen, recovery codes. Estimated four to five hours.

**Phase 3: qui Integration and Torrent Visibility.** qui reverse-proxy adapter with direct qBittorrent fallback, torrent list and detail views, WebSocket live updates. Estimated four to six hours.

**Phase 4: Event Bus and Webhook Ingress.** Internal event bus, webhook receivers with HMAC, autobrr/qBit/cross-seed webhook subscriptions, event-driven snapshot updates. Estimated six to eight hours.

**Phase 5: autobrr Read Integration and Filter Performance.** autobrr API adapter, filter list and recent activity views, filter performance correlation. Estimated six to eight hours.

**Phase 6: Push Notifications and Rules Engine.** VAPID setup, push subscriptions, rules engine evaluating against event bus and snapshots, built-in trigger types, cooldown handling, multi-channel delivery. Estimated five to six hours.

**Phase 7: Intelligence Features.** Ratio velocity, hit-and-run risk prediction, dead swarm detection, disk forecasting with Monte Carlo intervals, recommendations engine with provenance trails. Estimated eight to ten hours.

**Phase 8: Simulation and What-If Engine.** Sandboxed evaluation context, simulation table with named runs and annotations, UI for defining candidate rule changes and comparing results, promotion to production. Estimated eight to ten hours.

**Phase 9: tqm and cross-seed Integration.** tqm rule management via configuration directory, recording of tqm run outputs, cross-seed configuration management and activity dashboard. Estimated six to eight hours.

**Phase 10: Health Budget Framework.** Budget definitions in YAML, burn-rate tracking, dashboard display of consumption and projected exhaustion, budget-aware notification rules. Estimated four to six hours.

**Phase 11: Tracker Rule Corpus.** Schema design and validation, initial corpus entries, warning generation against operator actions, community contribution documentation. Estimated six to eight hours.

**Phase 12: Local LLM Conversational Layer.** Ollama integration with structured tool calling, conversation persistence, mobile chat UI, privacy-redaction layer, iOS Shortcuts integration. Estimated ten to twelve hours.

**Phase 13: Multi-Armed Bandit Filter Tuning.** Variant filter definitions, traffic splitting and outcome recording, confidence-interval reporting, variant promotion. Estimated six to eight hours.

**Phase 14: Self-Observability Polish.** Prometheus metrics endpoint, Loki-compatible structured logs, OpenTelemetry traces, observability stack documentation. Estimated four to six hours.

**Phase 15: Polish and Extension.** Loading and error and empty states throughout, iOS-specific polish, onboarding flow, settings interface, data export, backup automation with age encryption and off-host upload, generic webhook receivers, audit log enhancements, emergency mode. Estimated eight to ten hours.

Total estimated effort: roughly ninety-five to one hundred thirty-five hours of focused engineering work for a complete system across all phases. Realistic calendar duration depends on weekly engineering capacity. A weekend per phase yields a complete system in approximately fifteen weekends, though phases can be reordered or skipped based on operator priority.

---

## 10. Operational Concerns

### 10.1 Backup and Recovery

The SQLite database is backed up daily by a backend cron job to a local backups directory. The last thirty daily backups are retained on a rolling basis. The DuckDB analytical store is rebuilt from snapshots on demand and is not separately backed up.

The configuration-as-code directory is backed up by virtue of being a git repository pushed to a private remote.

Encrypted off-host backups run weekly by default. The local backup is age-encrypted to the operator's age identity and uploaded to a chosen object storage destination. Cloudflare R2 and Backblaze B2 are both inexpensive enough to be effectively free at this scale.

Recovery from a local backup involves stopping the service, replacing the database file with the chosen backup, and restarting. Recovery from an off-host backup involves downloading the encrypted blob, decrypting with the age identity, and proceeding as with a local backup.

### 10.2 Updates and Maintenance

Code updates follow a standard pull-build-restart sequence. Because the Go binary is statically linked and the frontend is embedded, "build" is a single `go build` that produces a new binary; there is no separate frontend build to coordinate, no dependency tree to refresh, and no service-wrapper indirection to navigate.

Database migrations are versioned and applied automatically on service start. Migrations are designed to be backward compatible where possible (additive only; new columns nullable; new tables) so that a failed migration does not destroy the running system. Rollback is via backup restoration.

### 10.3 Monitoring the Monitor

Three layers of mitigation are recommended for the Command Center's own failure modes.

The first layer is internal: health endpoint exposes liveness and readiness, Prometheus metrics expose integration health, systemd unit configured for auto-restart on failure with rate-limited restart attempts.

The second layer is external uptime monitoring. A free or low-cost service (Uptime Kuma self-hosted, Healthchecks.io, or BetterStack) pings the Tailscale-only endpoint over a Tailscale-connected probe node and alerts by email or SMS on failure.

The third layer is a heartbeat file: the Command Center writes a timestamp file every minute, and an independent shell script run by cron checks for staleness.

### 10.4 Identity and Privacy

The Command Center exposes one identity boundary worth explicit acknowledgment. The host that runs the Command Center is associated with the operator's primary identity, and the seedbox services it observes may be associated with a separate identity for privacy purposes. Running the Command Center on the primary-identity host creates a logical link between these identities at the network and application layers.

For most operators this is acceptable; the link exists on equipment they fully control and is not exposed externally. Operators with stricter identity separation requirements should host the Command Center on infrastructure that aligns with the seedbox identity rather than the primary identity.

The system defaults to local-only inference for the conversational layer specifically because privacy is a frequent and legitimate concern. Remote inference is available but never the default.

### 10.5 Disaster Scenarios

Host failure is recovered through backup restoration plus credential re-enrollment from a desktop browser. Mobile device loss is recovered through PWA reinstallation and re-authentication. Tracker site outage is detected by scrape failures, surfaces stale-data indicators, and continues normal operation. Torrent client outage is detected by event bus silence plus health checks. Tailscale outage isolates the operator from the host but causes no data loss.

Configuration corruption is recoverable through `git revert`. The Command Center additionally retains an in-memory copy of the last-known-good configuration and continues running on it if a hot-reload validation fails.

---

## 11. Technology Decisions Summary

| Concern | Decision | Rationale |
|---|---|---|
| Host OS | Linux (Debian, Ubuntu, NixOS) | Aligns with ecosystem; native Docker; systemd for service management |
| Backend language | Go | Single static binary; ecosystem alignment with autobrr, qui, tqm |
| Backend framework | Echo or Chi | Idiomatic Go; small dependency surface |
| State database | SQLite | Single-file; mature; well-supported |
| Analytical database | DuckDB embedded | Columnar; vectorized; ideal for snapshot analytics |
| Frontend framework | React 18 with Vite | Mature; great PWA tooling |
| Component library | shadcn/ui | Accessible baseline; customizable |
| Styling | Tailwind CSS | Utility-first; mobile-responsive |
| State management | TanStack Query + Zustand | Server and client state separated correctly |
| Real-time | WebSocket + SSE hybrid | Bidirectional for actions, one-way for streams |
| Authentication | WebAuthn passkeys | Biometric on mobile; no password fatigue |
| Networking | Tailscale | Mesh VPN; zero-config NAT traversal |
| Process management | systemd unit | Native; reliable |
| Logging | zerolog or zap with redaction | Structured; fast; explicit redaction primitives |
| Secrets at rest | age-encrypted blobs in SQLite | Cross-platform; keyed to platform keychain |
| Configuration | YAML files in git directory | Declarative; version-controlled; reviewable |
| Push notifications | Web Push Protocol | Standard; iOS 16.4+ |
| Cross-seeding | Delegated to cross-seed daemon | Mature tool; webhook-driven integration |
| Cleanup automation | Delegated to tqm | Mature rule engine |
| Anti-bot bypass | Byparr fallback | FlareSolverr replacement for Cloudflare-protected trackers |
| Optional LLM | Ollama (local) | Privacy-first; no recurring cost |
| Off-host backup | Cloudflare R2 or Backblaze B2 | Cheap; encrypted before upload via age |
| Self-observability | Prometheus, Loki, OpenTelemetry | Standard; opt-in |

---

## 12. Risks and Open Questions

Tracker site HTML changes can break scraping. Mitigation is implemented per integration with structured error reporting that distinguishes network, authentication, and structural failures rather than producing silent stale data.

Cloudflare and other anti-bot escalation can break Byparr in turn. Keep Byparr updated, treat scrape failures as first-class events that surface to the operator.

Push notification quotas imposed by browser vendors and platform push services are unlikely to be a constraint at single-user scale.

LLM hallucination is a meaningful risk for the conversational layer. Mitigation comes from grounding (every answer must be supported by tool-call data), explicit provenance display, and defaulting to small local models calibrated for not over-claiming.

Service drift over a multi-phase project is mitigated by producing a usable deliverable at each phase boundary.

Identity linkage between the operator's primary identity and the seedbox identity is created by the chosen deployment topology and acknowledged as a deliberate trade-off.

---

## 13. Success Criteria

The system is considered successfully delivered when the following conditions are jointly met.

The PWA installs cleanly on the operator's mobile device home screen and launches in standalone mode indistinguishable from a native application. All integrated services are reachable through the Command Center interface, and data displayed matches the corresponding services' native interfaces. Push notifications are delivered reliably to subscribed devices within ten seconds of triggering conditions. The system survives a host reboot and resumes operation without manual intervention. Authentication via passkey works reliably on the operator's mobile device using biometric unlock.

Beyond these table-stakes conditions, the system is successful when the differentiated features change operator behavior in measurable ways. Simulation runs before rule changes become routine practice. Decision provenance trails are consulted when recommendations are uncertain. The conversational layer answers questions that would previously have required manual database queries. Health budgets surface problems before they become incidents. Filter performance correlation produces tightening or relaxation recommendations that the operator finds accurate.

The strongest success signal is qualitative: the operator finds themselves opening the Command Center first when something is happening with their seedbox, instead of opening five different service tabs.

---

## Appendix A: Database Schema

```sql
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA synchronous = NORMAL;
PRAGMA cache_size = -64000;
PRAGMA temp_store = MEMORY;

-- Encrypted credential storage. Values are age-encrypted blobs decrypted on demand.
CREATE TABLE secrets (
    key TEXT PRIMARY KEY,
    encrypted_value BLOB NOT NULL,
    updated_at INTEGER NOT NULL,
    rotation_due_at INTEGER
);

CREATE TABLE webauthn_credentials (
    id TEXT PRIMARY KEY,
    public_key BLOB NOT NULL,
    counter INTEGER NOT NULL,
    transports TEXT,
    name TEXT,
    created_at INTEGER NOT NULL,
    last_used_at INTEGER
);

CREATE TABLE recovery_codes (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    code_hash BLOB NOT NULL,
    used_at INTEGER,
    created_at INTEGER NOT NULL
);

CREATE TABLE sessions (
    token TEXT PRIMARY KEY,
    created_at INTEGER NOT NULL,
    expires_at INTEGER NOT NULL,
    last_seen_at INTEGER NOT NULL,
    user_agent TEXT,
    ip_address TEXT
);

CREATE TABLE push_subscriptions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    endpoint TEXT UNIQUE NOT NULL,
    p256dh TEXT NOT NULL,
    auth TEXT NOT NULL,
    user_agent TEXT,
    device_label TEXT,
    created_at INTEGER NOT NULL,
    last_delivery_at INTEGER,
    failure_count INTEGER NOT NULL DEFAULT 0
);

-- Configured external services
CREATE TABLE trackers (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    type TEXT NOT NULL,
    base_url TEXT NOT NULL,
    config_json TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    scrape_interval_seconds INTEGER NOT NULL DEFAULT 300,
    scrape_jitter_seconds INTEGER NOT NULL DEFAULT 60,
    use_byparr INTEGER NOT NULL DEFAULT 0,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
);

CREATE TABLE torrent_clients (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    type TEXT NOT NULL,
    base_url TEXT NOT NULL,
    proxy_via_qui INTEGER NOT NULL DEFAULT 0,
    config_json TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    poll_interval_seconds INTEGER NOT NULL DEFAULT 30,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
);

CREATE TABLE automation_tools (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    type TEXT NOT NULL,
    base_url TEXT NOT NULL,
    config_json TEXT NOT NULL,
    webhook_token TEXT,
    enabled INTEGER NOT NULL DEFAULT 1,
    poll_interval_seconds INTEGER NOT NULL DEFAULT 60,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
);

-- Simulation runs (declared before snapshot tables that reference it)
CREATE TABLE simulation_runs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    annotation TEXT,
    proposed_config_json TEXT NOT NULL,
    window_start INTEGER NOT NULL,
    window_end INTEGER NOT NULL,
    started_at INTEGER NOT NULL,
    completed_at INTEGER,
    status TEXT NOT NULL,
    summary_json TEXT,
    promoted_at INTEGER
);

-- Time series tables
CREATE TABLE ratio_snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    tracker_id TEXT NOT NULL REFERENCES trackers(id),
    timestamp INTEGER NOT NULL,
    simulation_id INTEGER REFERENCES simulation_runs(id),
    real_uploaded_bytes INTEGER,
    real_downloaded_bytes INTEGER,
    real_ratio REAL,
    displayed_uploaded_bytes INTEGER,
    displayed_downloaded_bytes INTEGER,
    displayed_ratio REAL,
    bonus_points INTEGER,
    unsat_count INTEGER,
    unsat_limit INTEGER,
    class_or_rank TEXT,
    raw_json TEXT
);
CREATE INDEX idx_ratio_snapshots_tracker_time
    ON ratio_snapshots(tracker_id, timestamp DESC);
CREATE INDEX idx_ratio_snapshots_simulation
    ON ratio_snapshots(simulation_id) WHERE simulation_id IS NOT NULL;

CREATE TABLE torrent_snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    info_hash TEXT NOT NULL,
    client_id TEXT NOT NULL REFERENCES torrent_clients(id),
    timestamp INTEGER NOT NULL,
    simulation_id INTEGER REFERENCES simulation_runs(id),
    uploaded_bytes INTEGER,
    downloaded_bytes INTEGER,
    state TEXT,
    ratio REAL,
    seeders INTEGER,
    leechers INTEGER,
    upload_speed_bps INTEGER,
    download_speed_bps INTEGER
);
CREATE INDEX idx_torrent_snapshots_hash_time
    ON torrent_snapshots(info_hash, timestamp DESC);
CREATE INDEX idx_torrent_snapshots_client_time
    ON torrent_snapshots(client_id, timestamp DESC);

CREATE TABLE torrents (
    info_hash TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    size_bytes INTEGER NOT NULL,
    category TEXT,
    tags TEXT,
    first_seen_at INTEGER NOT NULL,
    last_seen_at INTEGER NOT NULL,
    source_filter_id TEXT,
    source_tracker_id TEXT REFERENCES trackers(id),
    cross_seed_origin_hash TEXT,
    deleted_at INTEGER
);

CREATE TABLE torrent_trackers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    info_hash TEXT NOT NULL REFERENCES torrents(info_hash),
    tracker_id TEXT NOT NULL REFERENCES trackers(id),
    seed_time_required_seconds INTEGER,
    seed_time_accumulated_seconds INTEGER DEFAULT 0,
    h_and_r_risk_at INTEGER,
    UNIQUE(info_hash, tracker_id)
);

CREATE TABLE filter_performance (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    automation_tool_id TEXT NOT NULL REFERENCES automation_tools(id),
    filter_external_id TEXT NOT NULL,
    filter_name TEXT NOT NULL,
    variant_id TEXT,
    info_hash TEXT,
    release_name TEXT,
    grabbed_at INTEGER NOT NULL,
    final_uploaded_bytes INTEGER,
    final_ratio REAL,
    last_measured_at INTEGER
);
CREATE INDEX idx_filter_performance_filter
    ON filter_performance(automation_tool_id, filter_external_id);
CREATE INDEX idx_filter_performance_variant
    ON filter_performance(automation_tool_id, filter_external_id, variant_id);

-- Notifications
CREATE TABLE notification_rules (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    trigger_type TEXT NOT NULL,
    trigger_config_json TEXT NOT NULL,
    channels_json TEXT NOT NULL DEFAULT '["push"]',
    cooldown_seconds INTEGER NOT NULL DEFAULT 3600,
    last_fired_at INTEGER,
    source TEXT NOT NULL DEFAULT 'yaml',
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
);

CREATE TABLE notification_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    rule_id INTEGER REFERENCES notification_rules(id),
    title TEXT NOT NULL,
    body TEXT,
    sent_at INTEGER NOT NULL,
    delivered INTEGER NOT NULL DEFAULT 0,
    channel TEXT,
    subscription_id INTEGER REFERENCES push_subscriptions(id),
    error_message TEXT
);

-- Decision provenance
CREATE TABLE decision_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp INTEGER NOT NULL,
    decision_type TEXT NOT NULL,
    subject_type TEXT NOT NULL,
    subject_id TEXT NOT NULL,
    recommendation TEXT NOT NULL,
    confidence REAL,
    provenance_json TEXT NOT NULL,
    alternatives_json TEXT,
    operator_action TEXT,
    operator_action_at INTEGER
);
CREATE INDEX idx_decision_log_time ON decision_log(timestamp DESC);
CREATE INDEX idx_decision_log_subject ON decision_log(subject_type, subject_id);

-- Health budgets
CREATE TABLE health_budget_state (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    budget_name TEXT NOT NULL,
    period_start INTEGER NOT NULL,
    period_end INTEGER NOT NULL,
    consumed REAL NOT NULL DEFAULT 0,
    capacity REAL NOT NULL,
    last_updated_at INTEGER NOT NULL,
    UNIQUE(budget_name, period_start)
);

-- Tracker rule corpus
CREATE TABLE tracker_rules (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    tracker_id TEXT NOT NULL REFERENCES trackers(id),
    rule_key TEXT NOT NULL,
    rule_value_json TEXT NOT NULL,
    source_file TEXT,
    loaded_at INTEGER NOT NULL,
    UNIQUE(tracker_id, rule_key)
);

-- Optional LLM conversation history
CREATE TABLE llm_conversations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    started_at INTEGER NOT NULL,
    last_message_at INTEGER NOT NULL,
    title TEXT,
    pinned INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE llm_messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    conversation_id INTEGER NOT NULL REFERENCES llm_conversations(id),
    role TEXT NOT NULL,
    content TEXT NOT NULL,
    tool_calls_json TEXT,
    timestamp INTEGER NOT NULL
);
CREATE INDEX idx_llm_messages_conversation
    ON llm_messages(conversation_id, timestamp);

-- Operational tables
CREATE TABLE audit_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp INTEGER NOT NULL,
    actor TEXT NOT NULL,
    action TEXT NOT NULL,
    target_type TEXT,
    target_id TEXT,
    details_json TEXT,
    ip_address TEXT,
    user_agent TEXT
);
CREATE INDEX idx_audit_log_time ON audit_log(timestamp DESC);

CREATE TABLE system_events (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp INTEGER NOT NULL,
    level TEXT NOT NULL,
    component TEXT NOT NULL,
    message TEXT NOT NULL,
    context_json TEXT,
    correlation_id TEXT
);
CREATE INDEX idx_system_events_time ON system_events(timestamp DESC);
CREATE INDEX idx_system_events_level_time ON system_events(level, timestamp DESC);
CREATE INDEX idx_system_events_correlation ON system_events(correlation_id) WHERE correlation_id IS NOT NULL;

CREATE TABLE webhook_endpoints (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    secret_token TEXT NOT NULL,
    enabled INTEGER NOT NULL DEFAULT 1,
    handler_type TEXT NOT NULL,
    handler_config_json TEXT,
    created_at INTEGER NOT NULL,
    last_called_at INTEGER
);

CREATE TABLE schema_migrations (
    version INTEGER PRIMARY KEY,
    applied_at INTEGER NOT NULL,
    name TEXT NOT NULL
);
```

---

## Appendix B: API Surface Summary

```
AUTHENTICATION
POST   /api/auth/webauthn/register/start
POST   /api/auth/webauthn/register/finish
POST   /api/auth/webauthn/login/start
POST   /api/auth/webauthn/login/finish
POST   /api/auth/logout
GET    /api/auth/me
POST   /api/auth/recovery                       (no session, requires recovery code)

TORRENTS (proxied through qui when configured)
GET    /api/torrents
GET    /api/torrents/:hash
GET    /api/torrents/:hash/history
POST   /api/torrents/add
POST   /api/torrents/:hash/pause
POST   /api/torrents/:hash/resume
POST   /api/torrents/:hash/recheck
DELETE /api/torrents/:hash?deleteFiles=true|false
PATCH  /api/torrents/:hash

TRACKERS
GET    /api/trackers
GET    /api/trackers/:id
GET    /api/trackers/:id/snapshots?range=...
GET    /api/trackers/:id/current
GET    /api/trackers/:id/rules
POST   /api/trackers/:id/refresh
POST   /api/trackers
PATCH  /api/trackers/:id
DELETE /api/trackers/:id

AUTOMATION
GET    /api/automation/tools
GET    /api/automation/tools/:id/status
GET    /api/automation/filters
GET    /api/automation/filters/:id
PATCH  /api/automation/filters/:id
GET    /api/automation/filters/:id/performance
GET    /api/automation/filters/:id/variants
GET    /api/automation/releases?filter=...

CROSS-SEED
GET    /api/cross-seed/activity
GET    /api/cross-seed/stats
POST   /api/cross-seed/search

TQM
GET    /api/tqm/recent-runs
POST   /api/tqm/dry-run

INTELLIGENCE
GET    /api/intelligence/ratio-velocity
GET    /api/intelligence/h-and-r-risk
GET    /api/intelligence/dead-swarms
GET    /api/intelligence/disk-forecast
GET    /api/intelligence/recommendations

SIMULATION
POST   /api/simulation/runs
GET    /api/simulation/runs
GET    /api/simulation/runs/:id
GET    /api/simulation/runs/:id/results
DELETE /api/simulation/runs/:id
POST   /api/simulation/runs/:id/promote

DECISIONS
GET    /api/decisions
GET    /api/decisions/:id
POST   /api/decisions/:id/acknowledge
POST   /api/decisions/:id/apply
POST   /api/decisions/:id/dismiss

HEALTH BUDGETS
GET    /api/budgets
GET    /api/budgets/:name/state
GET    /api/budgets/:name/history

NOTIFICATIONS
POST   /api/push/subscribe
DELETE /api/push/subscribe/:id
GET    /api/push/subscriptions
GET    /api/notifications/rules
POST   /api/notifications/test
GET    /api/notifications/log

LLM (optional, present only when enabled)
POST   /api/llm/conversations
GET    /api/llm/conversations
GET    /api/llm/conversations/:id
POST   /api/llm/conversations/:id/messages
DELETE /api/llm/conversations/:id

EMERGENCY
POST   /api/emergency/activate
POST   /api/emergency/deactivate
GET    /api/emergency/state

WEBHOOKS
GET    /api/webhooks
POST   /api/webhooks
PATCH  /api/webhooks/:id
DELETE /api/webhooks/:id
POST   /webhook/:id                             (no session, token in body)

SHORTCUTS (iOS)
GET    /api/shortcuts/ratio/:tracker_id
GET    /api/shortcuts/torrents/count
GET    /api/shortcuts/disk-usage
GET    /api/shortcuts/recent-activity
POST   /api/shortcuts/ask                       (gateway to LLM)

SYSTEM
GET    /api/system/health
GET    /api/system/events?level=...&component=...
GET    /api/system/audit-log
POST   /api/system/backup
GET    /api/system/exports?format=csv|json
GET    /metrics                                 (Prometheus)

REAL-TIME
WS     /ws                                      (subscribe to event topics)
GET    /sse/events                              (server-sent events stream)
```

---

## Appendix C: Notification Trigger Types

The notification rules engine supports the following built-in trigger types. Each accepts a configuration object with type-specific fields, documented in the YAML schema for each rule type.

The full list includes: ratio threshold (fires when real or displayed ratio crosses a configured value), ratio velocity change (fires when ratio change rate exceeds configured bounds), unsatisfied threshold (fires when unsatisfied count approaches a tracker's limit), hit-and-run risk imminent (fires when any torrent enters the configured warning window before its risk timestamp), automation disconnected (fires when an automation tool reports disconnection from announce sources beyond a grace period), client unreachable (fires when a torrent client cannot be reached beyond a grace period), filter grab (fires on every automation filter match, intended for monitoring and typically cooldown-restricted), torrent completed (fires on completion with optional minimum size and category filters), disk threshold (fires when disk usage crosses a configured value), tracker scrape error (fires when scraping a tracker repeatedly fails, with distinct classifications for network, authentication, and structural failure), health budget burn (fires when budget consumption rate exceeds a configured value relative to time elapsed), and decision recommendation (fires when the system produces a recommendation above a configured severity threshold).

For operators who need behavior not covered by the built-in types, a custom-query trigger evaluates an operator-supplied SQL query against the database and fires based on the result, with appropriate sandboxing.

---

## Appendix D: Recommended Initial Notification Rules

The system ships with the following notification rules disabled by default. Operators are encouraged to enable a conservative subset initially and add more over time based on experience.

A "real ratio dropped below threshold" rule with one trigger per tracker, threshold typically zero point two zero for trackers requiring positive ratio, cooldown twenty-four hours. An "unsatisfied count approaching limit" rule with one trigger per tracker, threshold at ninety percent of limit, cooldown six hours. A "hit-and-run risk in twenty-four hours" rule covering all torrents and trackers, with no cooldown because each torrent only fires once per risk window. An "automation tool disconnected for ten minutes" rule per automation tool, with grace period ten minutes and cooldown one hour. A "torrent client unreachable for ten minutes" rule per client, similar parameters. A "disk usage above eighty-five percent" rule per monitored disk, threshold eighty-five percent, cooldown twelve hours. A "tracker scrape failed three times consecutively" rule per tracker, threshold three, cooldown six hours. A "health budget burn rate exceeded" rule per defined budget, severity threshold proportional to remaining period.

---

## Appendix E: Tracker Rule Corpus Schema

The tracker rule corpus is stored as YAML files in the configuration directory, one file per tracker. The schema captures the structural rules a tracker enforces in a machine-readable format that the Command Center can reason about.

```yaml
# trackers/example-tracker.yaml
tracker_id: example
name: ExampleTracker
home_url: https://example.tracker
seed_time_requirements:
  default_hours: 120
  by_size:
    - max_size_gb: 5
      hours: 96
    - max_size_gb: 20
      hours: 168
    - hours: 336        # everything above falls here
hit_and_run:
  rule: "not seeded the required time"
  grace_period_hours: 12
  consequences: warning_then_suspension
  max_violations_before_suspension: 3
trump_rules:
  - "Internal releases trump scene"
  - "P2P releases of internal-eligible content removed within 48h of internal upload"
  - "Higher bitrate trumps lower bitrate within same encoding family"
group_policies:
  blacklisted_groups: []
  preferred_groups:
    - INTERNAL
freeleech:
  signal_types:
    - sitewide_global
    - personal_token
    - tag_based
  detection_method: scrape_torrent_page
  bonus_point_exchange_rate: 250  # points per FL token
ratio_economy:
  type: bonus_points
  uploaded_credit_per_byte_seeded: 0.0001
  point_conversion_rates:
    - to: freeleech_token
      cost: 50000
    - to: upload_credit_gb
      cost: 10000
inactivity:
  account_inactivity_warning_days: 60
  account_inactivity_disable_days: 90
parking:
  allowed: true
  max_days: 120
notes: |
  Hand-edited notes about the tracker that don't fit elsewhere.
  Useful for operator-specific reminders.
```

The corpus loader validates each file against this schema at startup and on hot-reload. Validation failures are logged and the previous valid corpus remains in effect. The `tracker_rules` SQL table caches the loaded corpus for query, indexed by tracker and rule key.

---

## Appendix F: LLM System Prompt Template

The local language model receives a system prompt and a set of tool definitions on every conversation. The system prompt is templated and operator-customizable; the default template is given below.

```
You are the Seedbox Command Center's data-grounded assistant. The operator
asks questions about their seedbox; you answer using the tools available to
you to query their actual data. Never speculate or pattern-match from prior
knowledge about seedbox best practices when you have a tool that can give
you the actual answer.

Your goals, in order:
  1. Answer the operator's question accurately based on their data.
  2. Show your work briefly when the answer required non-obvious analysis.
  3. Surface caveats when the data is incomplete or stale.
  4. Suggest a concrete next action when the situation warrants one.

Style:
  - Mobile-first: keep answers short unless detail is requested.
  - Use the operator's terminology (tracker names, filter names, categories).
  - Avoid jargon the operator hasn't used first.
  - Never claim to have taken an action you haven't.

Available tools (selection):
  - get_tracker_ratio(tracker_id, window)
  - get_torrent_summary(filters)
  - get_filter_performance(filter_id, window)
  - get_hr_risk()
  - get_recent_decisions(limit)
  - get_dead_swarms()
  - get_disk_forecast()
  - get_budget_state(budget_name)
  - get_audit_log(filters)

If a question requires an action (pause a torrent, change a filter), do not
attempt the action. Instead, describe the action and ask the operator to
confirm in the UI. The conversational layer is read-only by design.
```

This template prioritizes grounding over fluency, brevity over comprehensiveness, and operator control over autonomy. The conversational layer is positioned as a productivity tool, not as an autonomous agent.

---

## End of Specification