12 KiB
Actionable implementation plan
Implementation status (2026-07-23): phases 0–7 are implemented in the proto, control, and client worktrees. Phase 8 has a passing local 2×2 happy path, deterministic selective/recovery matrix, and adversarial v2/fault matrix: complementary partial placements merge correctly, eviction uses archive coverage unions, queued and active cancellation preserve their safety boundaries, restarts replay durable work, and hostile preconditions fail without committing. Pure-v2/hybrid round trips, recoverable stalls, capacity-guard failure, and duplicate/reordered WebSocket events also pass from a clean topology. The remaining fixture and fault categories listed below are still release gates; phase 9 remains intentionally deferred until the complete release matrix passes.
No application implementation begins until Phase 0 is reviewed. Within every later phase, tests and documentation are part of the deliverable rather than a follow-up task.
Phase 0 — Freeze the contract
Actions
- Convert the decision ledger into the system, workflow, safety, service API, deployment, Telegram, and test documents in this directory.
- Create the independent
archive-control-protoGit repository. - Define all v1 resource, selection, route, inventory, job, progress, command/event, reconciliation, and envelope messages.
- Add canonical protobuf-JSON examples for registration, inventory queries, route creation, transfer start/progress/commit, cancellation, eviction, and recovery snapshots.
- Run Dockerized Buf format/lint and protoc descriptor compilation.
- Configure the Gitea remote
cabbage/archive-control-protowith its dedicated SSH key. Tag the reviewed baselinev0.1.0only after review.
Acceptance gate
- No unresolved
TODOchanges wire meaning or destructive behavior. - Every workflow transition maps to a protobuf command/event.
- JSON examples parse through generated descriptors.
- Documentation and protobuf enums/state machines agree.
Phase 1 — Build the control core inside mogic-bot
Actions
- Add
archive_control/parallel toremote_control/, with no imports from Telegram in its core service, scheduler, protocol, persistence, or orchestration layers. - Add strict configuration parsing to the existing bot configuration, including shared token, WebSocket bind/path, database/backup paths, heartbeat, scheduling, route policy, retention, and Telegram refresh knobs.
- Add SQLite schema migrations for clients, connections, routes, jobs, commands, job events, reservations, placements, UI sessions, and tombstones. Enable foreign keys, WAL, busy timeout, and transactional schema migration.
- Implement online backups, integrity verification, retention, and offline verify/restore commands.
- Implement WebSocket registration-first authentication, duplicate-client replacement, heartbeats, bounded send queues, envelope validation, and protocol version negotiation.
- Implement durable command outbox/inbox semantics, acknowledgements, per-job event sequencing, snapshots, and reconnect reconciliation.
- Implement the global resource reservation and FIFO per-client/per-route scheduler.
- Add the thin test-only HTTP adapter over
ArchiveControlService.
Acceptance gate
- Bot startup/shutdown cleanly owns Archive Control tasks in its existing asyncio lifecycle.
- Existing bot tests and workflows are unchanged.
- Restart, duplicate registration, lost acknowledgement, event duplication, sequence gap, and DB backup/restore tests pass.
- The test HTTP adapter can create/query/cancel synthetic jobs without Telegram.
Phase 2 — Build the archive-client foundation
Actions
- Scaffold
archive-clientswith asrc/layout, Python 3.11, strict TOML parsing, environment/file secrets, CLI overrides, structured redacted logs, and console entry points. - Implement
--mode archive|cache, stable client ID, state/backup DBs, and Docker-friendly shutdown handling. - Implement capped exponential reconnect with jitter, registration, heartbeat acknowledgements, one-writer WebSocket output, durable command deduplication, and active-job snapshots.
- Implement API-visible-to-local root mapping and path-confinement helpers.
- Probe qBittorrent, Syncthing, filesystem/link/reflink, sparse, and permission capabilities at startup; advertise a precise health/capability report.
- Add multi-stage Dockerfile and Compose example. Run unprivileged and mount only config, secrets, DB/backups, qB data, and sync data.
- Build amd64 first; keep the Dockerfile architecture-neutral for the release buildx phase.
Acceptance gate
- Invalid/unknown configuration fails before connecting.
- Secrets never appear in logs or SQLite.
- Restart and duplicate-command tests prove idempotent client behavior.
- Both daemon roles start from the same image and differ only by config/mode.
Phase 3 — Service adapters and on-demand discovery
Actions
- Implement qBittorrent cookie authentication and capability adapters for supported 4.5–4.6 and 5.x APIs.
- Implement on-demand torrent summaries, scoped hash lookup, lazy complete content trees, v1/v2 identity validation, torrent export, stopped add, selection application, recheck monitoring, and entry-only deletion.
- Implement Syncthing API-key authentication, version/config/status/events, folder/device discovery, per-file progress, need/file completion checks, and rescan support.
- Discover only healthy pairwise
sendreceiveroutes under the configured roots. - Implement idempotent device pairing and folder creation using advertised addresses, plus bidirectional nonce verification and 30-minute default timeout.
- Implement scoped, chunked, revisioned inventory and tree queries; do not add background cluster inventory polling.
Acceptance gate
- Contract fixtures cover API version differences and permanent/transient errors.
- A pair of isolated client/Syncthing stacks can auto-create and verify a route.
- Large synthetic inventories stream in bounded chunks and interrupted results are not committed by Titan.
Phase 4 — Safe file operation and manifest layer
Actions
- Define the on-disk job staging layout and atomically written protobuf-JSON transfer manifest/ready marker.
- Implement safe traversal using normalized torrent paths, no-follow checks, root confinement, regular-file-only validation, and duplicate/case-collision detection.
- Implement hardlink → reflink → safe-copy fallback with per-file journaling, progress, cancellation points, and restart reconciliation.
- Implement exact logical/allocated size accounting, free-space/reserve checks, and startup sparse capability enforcement.
- Implement target never-overwrite behavior and provenance for created versus pre-existing reused files.
- Implement tested qBittorrent/libtorrent partfile adapters, compatibility gates, baseline backups, and safe merge/rollback behavior.
Acceptance gate
- Tests cover cross-filesystem fallback, insufficient space, permission errors, symlink races, special files, path escape, pre-existing collisions, sparse artifacts, and supported/unsupported partfiles.
- Cancelling/restarting every file-operation phase leaves a deterministic journal and no uncontrolled deletion.
Phase 5 — Transfer orchestration
Actions
- Implement fresh source/target tree preflight, requested-selection expansion, immutable file-index snapshots, exact delta calculation, and no-op rejection.
- Implement source staging, per-job Syncthing progress, destination completion proof, and target materialization.
- Implement new-target stopped/manual add and existing-target baseline/delta transaction.
- Apply selected/skipped state only, recheck the union while stopped, monitor for any download attempt, and restore/start the correct run state.
- Commit placement generation only after verification; treat staging removal
as post-commit cleanup with retryable
CLEANUP_REQUIREDstate. - Implement cooperative cancellation/compensation for every pre-commit phase and non-destructive post-commit cleanup behavior.
Acceptance gate
- Full, selective, subset, and incremental merge scenarios pass in both directions.
- A target never downloads data during verification.
- Failure injection at every transition preserves source and target baselines.
- Archive jobs retain their source cache placements.
Phase 6 — Eviction and placement reconciliation
Actions
- Implement on-demand cache placement listing and fresh archive coverage sets.
- Require complete per-file coverage across one or more online archive nodes; expose disabled reasons and provide no force override.
- Implement eviction jobs: coverage recheck, qB entry-only removal, remaining torrent exact-path overlap snapshot, safe file unlink, partfile ownership check, and non-recursive bottom-up directory removal.
- Reconcile observed placements with successful job generations and external qBittorrent changes without background polling.
Acceptance gate
- Eviction cannot proceed with any uncovered selected file.
- Shared exact paths, shared directories, unknown files, and unrelated partfiles survive.
- Retrying after interruption is idempotent and an archive placement is never deleted.
Phase 7 — Telegram integration
Actions
- Add
ArchiveControlHandlerwithout changing existing handler behavior. - Implement Archive, Unarchive, Evict Cache, and Job Status flows with one edited message, pagers, chained literal filters, cancel/back behavior, and single-use confirmation nonces.
- Implement normal-all and one-entry tree selection; keep protobuf support for multiple selections.
- Persist UI sessions and restore live status subscriptions after bot restart.
- Inherit private-chat username authorization and optionally pin configured numeric IDs. Keep jobs shared and ownerless.
- Render five-step transfer and three-step eviction progress, including stale, waiting, stalled percentage, cleanup-required, failure reason, paging, Clear Finished, and Remove One.
- Coalesce routine edits to the configurable five-second default and handle Telegram rate limits.
Acceptance gate
- Telegram and test HTTP adapters produce identical service-layer outcomes.
- Stale/double callbacks are harmless.
- Bot restart preserves active UI/status behavior.
- Existing mogic-bot regression tests pass.
Phase 8 — Cross-project E2E and chaos testing
Actions
- Create five Compose projects under this repository's
e2e/: control, cache-1, cache-2, archive-1, and archive-2. - Give every data node isolated qBittorrent, Syncthing, archive-client, config, state, backup, and data volumes on one dedicated test network.
- Configure static advertised Syncthing service addresses while testing automatic device/folder pairing.
- Generate small v1/v2/hybrid torrents and fixtures for complementary and overlapping selections, partfiles, sparse files, path collisions, shared directories, and permission errors.
- Drive scenarios with curl against the test HTTP adapter.
- Inject control/client/service restarts, network loss, duplicate commands, stale snapshots, stalls, disk exhaustion, cancellation, and DB restore.
- Tear down only test resources identified by unique project names/labels.
Acceptance gate
- The complete acceptance matrix in
testing.mdpasses repeatedly without live infrastructure. - No test uses credentials or paths from x1, x2, lithium, or Titan production.
- Test teardown is scope-validated and leaves unrelated Docker resources alone.
Phase 9 — Release and staged deployment
Actions
- Run Buf breaking checks against the last proto release and tag compatible repositories.
- Build
sodium/archive-clientsfor linux/amd64 and linux/arm64 with OCI source/protocol labels; publish an immutable semantic-version tag and the stablelatestalias. - Record image digest and provide pinned Compose examples.
- Back up real qBittorrent/Syncthing and Archive Control state before a separately approved deployment.
- Deploy one cache/archive pair in observation mode, validate on-demand inventory and route discovery, then run a small non-critical canary transfer.
- Exercise recovery and eviction only after canary verification and explicit approval, then expand node-by-node.
Acceptance gate
- Multi-platform manifest resolves to tested images and the runtime image is reasonably small.
- Rollback instructions and prior images/config/DB backups are verified.
- No production qBittorrent/Syncthing global setting is modified.