Milestone 406 retires pixiv (rule 171) in two phases at the operator's explicit ask: switch it off, then later delete its code. This is the switch-off. Steps 2 and 3 ship together because each is a half-state of the other: unregistered but still in the extension, pixiv creator pages would offer a button the backend then refuses. Reachability removed, never gated (rule 22 - no flag, no `if platform == "pixiv"`): - platforms registry: pixiv unregistered, so /api/platforms, the source validator and quick-add all refuse it through their existing unknown-platform paths. - NATIVE_INGESTER_PLATFORMS: pixiv removed. - extension_service: pixiv's quick-add URL pattern removed (the Python half of the JS mirror). - extension: pixiv's host permissions, content-script match, platform entry and artist pattern removed; popup's pixiv branches removed; and the whole pixiv PKCE OAuth flow cut out of background.js. That last one could not wait for phase 2 - a webRequest listener on a host the manifest no longer grants is at best dead and at worst a startup failure for the entire background script. On startup the extension now also removes any pixiv refresh token a browser still holds in storage, for the same reason as the server-side credential cleanup (3980). - frontend: the extension card stops listing pixiv; SourceActions' copy of the native list drops it. platformColor keeps rendering a pixiv key so existing pixiv posts do not look broken. The guard, and why a registry change alone was not enough. A source outlives its platform: the live instance still had one ENABLED pixiv source (step 1). Tracing it: the scheduler only selects enabled rows and every platform lookup uses .get(), so a disabled row is inert - but re-enabling it and pressing Check would have routed pixiv, no longer native, straight into the gallery-dl branch, which still has a pixiv extractor. And a worker can pick up a still-enabled row before a deploy's migration runs. So run_download and verify_source_credential - the two functions every download and credential probe pass through - now refuse any platform not in the registry: an unsupported_url failure for downloads, and an inconclusive (None, not False) verify, since nothing was probed so nothing was rejected. Generic by registration, so it covers deviantart's leftovers too. Positive-controlled: a supported gallery-dl platform must still reach gallery-dl, or a guard that refused everything would pass (rule 167). Migration 0097 disables sources on retired platforms (pixiv, deviantart) and clears their failure state exactly as disabling through the app does (1285), so the stale row stops being scheduled and stops showing as failing. Nothing is deleted: removing a source can collide with uq_post_artist_external_id_null_source on real data, which is phase 2's step 6 to check. No post or image is touched. Tests: the known-platform lists drop pixiv and gain retirement assertions beside deviantart's; pixiv's positive extension cases become negative guards; the pixiv sidecar post-URL test is deleted with the behaviour it tested; quick-add rejects a pixiv URL. The pixiv client/downloader/ingester suites stay - that code stays until phase 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SHQB1YukL3VyvMK8rcbmV9
69 lines
2.9 KiB
Python
69 lines
2.9 KiB
Python
"""Disable sources on retired platforms, so the scheduler stops selecting them.
|
|
|
|
Milestone #406, phase 1 (switch pixiv off). Rule #171 records the scope decision.
|
|
|
|
## Why this is a migration and not a button
|
|
|
|
The live instance had one pixiv source still ENABLED when pixiv was retired
|
|
(read 2026-09-13, step 1) even though the operator believed it gone. Unregistering
|
|
a platform removes it from code; it does not touch the `source` rows that name it.
|
|
Left enabled, that row keeps being picked by the scheduler every interval, and
|
|
`download_backends` now refuses it with `unsupported_url` — forever, as a
|
|
climbing failure count on a source the operator has already given up.
|
|
|
|
A migration reaches the live instance on deploy without depending on anyone
|
|
finding the row and clicking it. The `run_download` guard is what makes a stale
|
|
enabled row SAFE; this is what makes it QUIET.
|
|
|
|
## Deliberately NOT done here
|
|
|
|
- **No rows are deleted.** Deleting a source sets its posts' `source_id` to NULL
|
|
(FK `ON DELETE SET NULL`), and `uq_post_artist_external_id_null_source` can
|
|
reject that if a source-less copy of one of those posts already exists. That
|
|
needs checking against real data first, which is phase 2's job (step 6). A
|
|
disable cannot collide with anything.
|
|
- **No posts or images are touched.** The art stays.
|
|
- **deviantart is included** because #3069 retired it and nothing disabled its
|
|
rows either. The read found none, so for it this is a no-op — written anyway,
|
|
so the statement names every retired platform rather than just the latest one.
|
|
|
|
## Hardcoded platform names
|
|
|
|
A migration is a record of one event, frozen in time, so it names the platforms
|
|
it acted on rather than importing today's registry — the registry will keep
|
|
changing and this revision must not.
|
|
|
|
Revision ID: 0097
|
|
Revises: 0096
|
|
Create Date: 2026-09-13
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "0097"
|
|
down_revision: Union[str, None] = "0096"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Clears the failure state the same way `SourceService.update` does when a
|
|
# source is disabled through the app (issue #1285), so a retired source
|
|
# does not keep showing as failing after it stops being polled. A disable
|
|
# done here and one done by clicking must leave identical rows.
|
|
op.execute(
|
|
"UPDATE source SET enabled = false, last_error = NULL, "
|
|
"error_type = NULL, consecutive_failures = 0 "
|
|
"WHERE enabled AND platform IN ('pixiv', 'deviantart')"
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
# Irreversible by design: which of these rows were enabled before is not
|
|
# recorded, and re-enabling every retired-platform source would resume
|
|
# polling services the product no longer supports. Rule #22 owes no
|
|
# migration story back to a dropped platform.
|
|
pass
|