Skip to content

Post-#13 follow-ups: fork version tracks upstream, qBittorrent cleanup contract - #14

Merged
alphyriver merged 3 commits into
mainfrom
refactor/qbittorrent-cleanup-contract
Aug 15, 2026
Merged

Post-#13 follow-ups: fork version tracks upstream, qBittorrent cleanup contract#14
alphyriver merged 3 commits into
mainfrom
refactor/qbittorrent-cleanup-contract

Conversation

@alphyriver

@alphyriver alphyriver commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Post-merge follow-ups to #13. Combines the qBittorrent cleanup-contract work with the fork-version fix, because merging the code alone would trigger Fork Image and mint another mislabelled release (supersedes #15).


1. Fork version tracks upstream again

The fork published v2.3.0.postN from 21 July while upstream shipped 2.4.0/2.4.1/2.4.2. v2.3.0.post7 was in fact upstream v2.4.1 plus the fork's work — v2.4.1 tags 2f4d237, exactly the commit #13 merged.

next-fork-version.sh was not at fault: it picks the nearest reachable plain upstream tag, and its reset-on-new-upstream behaviour is already covered by test_fork_version_sequence_and_upstream_reset. Its input was starvedactions/checkout brings only the fork's tags, and fork-upstream-sync.yml fetches upstream as main:refs/remotes/upstream/main with no --tags. Upstream release tags never entered the fork, so the base froze at v2.3.0 and the counter climbed.

Fetching them in the version job restores intended behaviour. Verified by running the exact CI sequence:

without the step:  v2.3.0.post8
with the step:     v2.4.1.post1

Tags land only in that ephemeral runner; the release job pushes the single computed tag and nothing runs push --tags, so the fork's published tag namespace and its tags: ["v*"] trigger are untouched. Mirroring upstream tags into the fork was considered and rejected — pushing v2.4.x would fire Fork Image once per upstream release.

2. qBittorrent cleanup contract — consistency

abort and discard_client_artifacts had near-identical bodies. SlskdRepository already factors that exact pair into _remove_transfer_records, so this mirrors it with _remove_unless_seeding(handle, action=...). All three adapters now match exactly: same 12 members, same kinds, signatures identical to DownloadClientProtocol.

3. The no-unlink invariant moved to the adapter

The if attempt.source == "torrent" branch added in #13 is removed. It was byte-for-byte the generic fall-through minus _cleanup_slskd_files — itself a no-op when materialized_paths is empty — verified by deleting it and watching every cleanup test still pass. It bought nothing and added a fourth source conditional to a service upstream rewrote ~700 lines of this sync.

The invariant now lives where it belongs: inspect_materialization routes every return through a single _materialization factory with no file_paths parameter, so it cannot be reintroduced one return-path at a time. Cleanup stays purely evidence-driven — unlink exactly what the client reported, nothing more.

Guards, verified by deliberately regressing the adapter

Made _materialization enumerate the content directory; 8 tests fail:

  • test_inspect_materialization_never_reports_file_paths across 7 torrent states, against a real populated mount (as first written it used a non-existent path and passed for the wrong reason);
  • test_materialization_factory_has_no_file_paths_parameter pins the signature;
  • the real-adapter integration test proves no seeded byte is deleted.

Plus a second integration test for the failed torrent path, which never calls abort (the service aborts only an active materialization) — so discard_client_artifacts is what removes it.

Tests

Backend suite green (6412 passed). Ruff clean. Frontend untouched.

🤖 Generated with Claude Code

…eanup end to end

Follow-up to #13. Two consistency and coverage gaps in the qBittorrent cleanup
contract that landed with the upstream merge.

Structure: `abort` and `discard_client_artifacts` had near-identical bodies.
`SlskdRepository` already factors exactly this pair into `_remove_transfer_records`,
so mirror it with `_remove_unless_seeding(handle, action=...)` in the internals
section. The public methods keep their distinct docstrings and signatures (the
protocol conformance test compares real signatures, not just names).

Coverage: the previous tests exercised the cleanup service against a fake client.
Added two integration tests that drive the REAL `QbittorrentDownloadClient` through
the real `AcquisitionCleanupService` and store:

- a completed torrent keeps both its files and its qBittorrent record, evidence is
  recorded from the remapped local path, and the attempt still reaches `complete`
  rather than retrying forever;
- a FAILED torrent is removed with its partial data. This path never calls `abort`
  (the service aborts only an `active` materialization), so `discard_client_artifacts`
  is what removes it - now pinned, and noted in the docstring.

No behaviour change: `_remove_unless_seeding` is the same logic both methods already
had, and the only visible difference is one merged log line that names the action.

Backend suite green (6404 passed). Ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

AI review is unavailable: DeepSeek API error: DEEPSEEK_API_KEY is not set

Please review this PR manually.

…pter

The `if attempt.source == "torrent"` branch added in #13 was redundant: removing
it leaves every cleanup test passing, because `_cleanup_slskd_files` iterates
`materialized_paths` and a torrent reports none. It duplicated 14 lines of the
generic fall-through and added a fourth source conditional to a service upstream
rewrote by ~700 lines this sync - a recurring conflict for no behaviour.

Removed it. Cleanup is now uniformly evidence-driven: unlink exactly the local
paths the client reported as attempt-owned, then discard its records.

The invariant that made the branch unnecessary is now enforced where it belongs.
`inspect_materialization` builds its result through a single private factory,
`_materialization`, which has NO `file_paths` parameter - so the field cannot be
reintroduced one return-path at a time, and anyone who needs it must change the
factory and read why it is absent.

Guards, verified by deliberately regressing the adapter to enumerate the content
directory: 7 parametrised adapter tests (one per qBittorrent state) plus the
real-client integration test all fail on that change. The adapter tests now use a
REAL populated mount - with a non-existent path they passed for the wrong reason.

Backend suite green (6412 passed). Ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…le copy

The fork has been publishing v2.3.0.postN since 21 July while upstream shipped
2.4.0, 2.4.1 and 2.4.2. v2.3.0.post7 is in fact upstream v2.4.1 plus the fork's
work - v2.4.1 tags 2f4d237, exactly the commit #13 merged.

next-fork-version.sh is not at fault: it picks the nearest reachable PLAIN
upstream tag, and its reset-on-new-upstream behaviour is already covered by
test_fork_version_sequence_and_upstream_reset. The input was starved.
`actions/checkout` brings only the FORK's tags, and fork-upstream-sync.yml
fetches upstream with `main:refs/remotes/upstream/main` - no --tags. So upstream
release tags never enter the fork, the base froze at the newest one that happened
to be here (v2.3.0), and the counter kept climbing.

Fetching them in the version job restores the intended behaviour:

  without the step:  v2.3.0.post8
  with the step:     v2.4.1.post1

The tags land only in that ephemeral runner. The release job pushes the single
computed tag (`git push origin "$TAG"`) and nothing anywhere runs `push --tags`,
so the fork's published tag namespace and its `tags: ["v*"]` build trigger are
unaffected. Mirroring upstream tags into the fork instead was considered and
rejected: pushing v2.4.x would fire Fork Image once per upstream release.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alphyriver alphyriver changed the title refactor(qbittorrent): factor shared abort/discard body, cover cleanup end to end Post-#13 follow-ups: fork version tracks upstream, qBittorrent cleanup contract Aug 15, 2026
@alphyriver
alphyriver merged commit 8f51c96 into main Aug 15, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant