Gated Patreon posts: skip on ingest + cleanup tool (#874) #112

Merged
bvandeusen merged 3 commits from dev into main 2026-06-16 15:25:34 -04:00
Owner

Tier-gated Patreon posts (current_user_can_view == False) were having their blurred locked-preview media downloaded as if real.

Ingester fix (#874)

  • PatreonClient.post_is_gated — gates only on an explicit current_user_can_view == False (missing/None → viewable, never over-filter).
  • ingest_core run() + preview() skip gated posts entirely: no media download, no post-record stub. Skipped before the #862 body canary so they don't inflate its sample; surfaced as N gated-skipped.

Cleanup tool (#874 follow-up)

A one-shot Settings → Maintenance action ("Clean up gated-post previews") to purge the blurred previews grabbed before the fix. The gated flag was never persisted, so it re-walks each enabled Patreon source (read-only) to re-derive the currently-served blurred filehashes, then matches by content hash against stored source_filehash — real content downloaded when access existed has a different hash and is provably spared. NULL hash → unverifiable, kept + reported. On apply: delete matched files, clear the seen/dead-letter ledger (so real media re-ingests if access returns), remove bare gated posts. Preview/apply share one predicate (rule 93).

Verified green on dev (CI run 1170, 5401512).

🤖 Generated with Claude Code

Tier-gated Patreon posts (`current_user_can_view == False`) were having their blurred locked-preview media downloaded as if real. ## Ingester fix (#874) - `PatreonClient.post_is_gated` — gates only on an explicit `current_user_can_view == False` (missing/None → viewable, never over-filter). - `ingest_core` `run()` + `preview()` skip gated posts entirely: no media download, no post-record stub. Skipped before the #862 body canary so they don't inflate its sample; surfaced as `N gated-skipped`. ## Cleanup tool (#874 follow-up) A one-shot Settings → Maintenance action ("Clean up gated-post previews") to purge the blurred previews grabbed before the fix. The gated flag was never persisted, so it re-walks each enabled Patreon source (read-only) to re-derive the currently-served blurred filehashes, then matches by **content hash** against stored `source_filehash` — real content downloaded when access existed has a different hash and is provably spared. NULL hash → unverifiable, kept + reported. On apply: delete matched files, clear the seen/dead-letter ledger (so real media re-ingests if access returns), remove bare gated posts. Preview/apply share one predicate (rule 93). Verified green on dev (CI run 1170, `5401512`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bvandeusen added 3 commits 2026-06-16 15:25:28 -04:00
fix(ingest): skip tier-gated Patreon posts entirely (#874)
CI / lint (push) Failing after 3s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Failing after 3m20s
b3afc2437e
Patreon serves only blurred locked-preview thumbnails for posts the
authenticated account can't fully view; the native ingester was
downloading those as real media. current_user_can_view was already in
_FIELDS_POST but never read.

Add PatreonClient.post_is_gated (gate ONLY on explicit
current_user_can_view=False; missing/None → viewable, never over-filter)
and skip gated posts at the top of the ingest_core run() and preview()
loops — no media download AND no post-record stub (operator: 'no stub
for gated content'). Skipped before the post-record block so gated posts
never inflate the #862 body canary; surfaced as 'N gated-skipped' in the
run summary. Same gate in preview() for preview/apply parity (rule 93).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test(ingest): repair gated-post tests (#874 CI bounce)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 27s
CI / integration (push) Successful in 3m18s
9422eadabe
The prior commit's Edit orphaned the recapture test's relink/stdout
assertions into the new preview test (F821 res_recap/downloader2/m1) and
the gated-skip test's written_paths check matched 'gated' in the tmp dir
name. Restore the recapture assertions to their function and assert on
the media basename instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(cleanup): purge misgrabbed gated-post blurred previews (#874 follow-up)
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 19s
CI / backend-lint-and-test (push) Successful in 31s
CI / integration (push) Successful in 3m18s
540151290b
A one-shot Maintenance action to remove the blurred locked-preview images
the ingester downloaded from tier-gated Patreon posts before #874.

current_user_can_view was never persisted, so the cleanup re-walks each
enabled Patreon source (read-only) to re-derive which posts are gated now
and the blurred filehashes Patreon serves for them, then matches by
CONTENT HASH against stored source_filehash. Because the hash is
content-addressed, a real file downloaded when access existed has a
different hash and can never match — regained-then-lost-access content is
provably spared (operator's hard requirement). NULL source_filehash =>
unverifiable, kept + reported.

On apply: delete matched ImageRecords + files (provenance cascades),
clear seen/dead-letter ledger rows for those hashes so the real media
re-ingests if access returns, and delete gated posts left bare. Shares
one match predicate between preview and apply (rule 93).

- cleanup_service: collect_gated_previews + purge_gated_previews
- tasks.admin: purge_gated_previews_task (async re-walk bridge, timeboxed)
- api.admin: POST /maintenance/purge-gated-previews
- GatedPurgeCard.vue in Settings > Maintenance (preview -> confirm -> apply)
- tests: collect predicate, hash-match delete/spare/unverifiable, ledger
  clear, bare-post removal, no-op

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bvandeusen merged commit 2b8ce86622 into main 2026-06-16 15:25:34 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bvandeusen/FabledCurator#112