Skip to content

fix: track collection membership provenance#2663

Merged
enoch85 merged 9 commits into
developmentfrom
fix/collection-membership-provenance
Apr 12, 2026
Merged

fix: track collection membership provenance#2663
enoch85 merged 9 commits into
developmentfrom
fix/collection-membership-provenance

Conversation

@enoch85

@enoch85 enoch85 commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #2611 by tracking rule-owned and manual collection membership separately. When multiple rules target the same manual media server collection, each Maintainerr collection now keeps its own rule-owned membership while still showing genuinely shared manual items.

Root cause

The old model stored a single isManual flag per CollectionMedia row. During media server sync, items discovered in a shared server collection but not tracked locally were imported as manual, even when they actually belonged to another rule targeting the same media server collection. That erased provenance and caused unrelated items to appear permanently pinned as manual.

What this changes

  • Replaces the single isManual flag with two independent fields:
    • includedByRule
    • manualMembershipSource (legacy, local, shared)
  • Preserves rule and manual membership independently so one can be removed without deleting the other.
  • Reconciles shared manual collections without re-importing rule-owned sibling items as manual.
  • Preserves local and legacy manual provenance during sync instead of collapsing everything into shared membership.
  • Applies scoped membership removal (rule, manual, all) so rule execution and media server sync only clear the intended membership.

Backward compatibility

isManual remains a derived field via entity hooks, so existing read paths continue to behave correctly while the new provenance fields drive collection membership behavior. The migration backfills includedByRule and manualMembershipSource from existing rows and remains reversible.

Test Instructions

Test 1

Test:
Create two Maintainerr rule groups that both target the same manual Plex or Jellyfin collection, then add media that only matches rule A.

Expected:
The item appears in rule A's Maintainerr collection only. It must not appear in rule B's Maintainerr collection as a manual item.

Old:
The item could bleed into rule B's Maintainerr collection and appear there as manual, even though it was only added by rule A.

Test 2

Test:
Add media that only matches rule B in the same shared manual collection setup.

Expected:
The inverse of test 1. The item appears in rule B's Maintainerr collection only and does not show up in rule A as manual.

Old:
The same cross-rule bleed could happen in the opposite direction, making rule-owned items look manually pinned in the sibling collection.

Test 3

Test:
Manually add an item directly in the media server collection shared by both Maintainerr collections.

Expected:
The item appears in both linked Maintainerr collections as manual/shared membership.

Old:
Manual and rule-owned provenance were not separated cleanly, so shared items and sibling rule-owned items could be conflated.

Test 4

Test:
Remove a rule match from an item that also has manual membership.

Expected:
The item remains in the collection because the manual membership still exists.

Old:
Removing the rule match could remove the entire row and lose the surviving manual membership.

Test 5

Test:
Remove a manually added item from the media server collection when that item is still rule-owned in Maintainerr.

Expected:
The manual membership is removed, but the item stays in the Maintainerr collection because the rule membership still exists.

Old:
The removal handling could treat the item as fully removed or otherwise corrupt the remaining membership state.

Test 6

Test:
Add an exclusion in one linked collection for an item present in the shared media server collection.

Expected:
That item is not re-imported into the excluded collection as shared manual membership.

Old:
Shared/manual reconciliation could re-import items too broadly and ignore the intended per-collection exclusion boundary.

Test 7

Test:
Run rule execution again after add/remove operations in a shared manual collection.

Expected:
Recently removed items are not resurrected, and recently added manual items are not dropped because of stale child enumeration from the media server.

Old:
Stale collection children could cause just-removed items to come back or just-added manual items to be cleared incorrectly.

@enoch85

enoch85 commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator Author

@Simon-Eklundh Please test this, polished it with the final fixes I think is valid. 👍

@enoch85

enoch85 commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator Author

/release-pr

@github-actions

Copy link
Copy Markdown
Contributor

Released to maintainerr/maintainerr:pr-2663 🚀

@Simon-Eklundh

Copy link
Copy Markdown
Contributor

just tested @enoch85

it seems to work to me at least.

I did need to run the rules and move out and into manual collection, but that was most likely because of an old rule having put it there, and I run two different instances depending on if I'm testing or not

@enoch85 enoch85 merged commit 34a24fc into development Apr 12, 2026
13 checks passed
@enoch85 enoch85 deleted the fix/collection-membership-provenance branch April 12, 2026 08:09
maintainerr-automation Bot added a commit that referenced this pull request Apr 12, 2026
* fix: quiet noisy service logs

* fix: clarify rule placeholders and Seerr helper copy

* fix: track collection membership provenance (#2663)

* fix: remove SSRF sinks from API failure logging (#2665)

* fix: remove SSRF sinks from API failure logging

Replace axios.getUri() calls in external-api, plexApi, and jellyfin-adapter
with a small helper that builds log-safe request descriptors via plain
string ops, and normalize base URLs on construction. Preserves per-request
baseURL overrides and params in failure logs, and closes the CodeQL
server-side-request-forgery alert on external-api.service.ts.

* fix: drop base URL normalizer to keep CodeQL SSRF fix minimal

The added normalizeExternalApiBaseUrl used new URL(baseUrl), which CodeQL
treats as a URL-construction sink. That gave its SSRF query a cleaner
dataflow path from user config into every downstream axios request,
producing four new alerts on the request call sites in
external-api.service.ts. Removing the normalizer keeps only
describeRequestTarget, which builds log strings via plain string ops
and encodeURIComponent, and closes the original alert without introducing
new ones.

---------

Co-authored-by: enoch85 <mailto@danielhansson.nu>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.6.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reused manual collection ui bug

2 participants