fix(e1): address /code-review high findings on the E1 stack
Review round on the whole E1 stack (main..HEAD). 6 findings, all low/medium, none blocking; fixed for a clean gate-green handoff. - plex/sync (correctness): the un-enriched cast guard compared the stored cast against THIS listing's length, so a trimmed Plex cast on a not-yet-enriched row was never applied. Compare against the listing CAP (_LISTING_CAST_CAP=20) instead: >cap = enriched (keep, survives a watermark reset), <=cap = listing-sourced (track edits in both directions). +2 regression tests. - main (security): add X-Frame-Options: DENY as the legacy-browser fallback for the CSP frame-ancestors clickjacking guard. - downloads/service (altitude): finalize_if_running now uses a SQLAlchemy 2.0 update() construct instead of a hand-built text() UPDATE string — same atomic WHERE status='running', idiomatic, no identifier interpolation, no manual StrEnum coercion. Drops the now-unused text import. - auth/admin (reuse): extract the SEC-10 "drop untrusted preset password + bump epoch" invariant into one helper (revoke_preset_password_if_unverified) so the Google-callback and admin-activation paths can't drift. - tests/migrations (robustness): guarantee the shared schema is restored to head in a finally, so a downgrade/re-upgrade failure can't cascade into later tests. - worker (simplification): align _finish_from_cache's display_name fill with the _download path's guard (no behavior change). Gate green: ruff clean, 334 pytest (0 skip), frontend unchanged.
This commit is contained in:
@@ -294,6 +294,11 @@ def _paginate(plex: PlexClient, key: str, item_type: int):
|
||||
break
|
||||
|
||||
|
||||
# The section listing exposes at most the top ~20 cast (Plex's own cap); a stored cast longer than this
|
||||
# can only have come from the rich enrich, never the listing.
|
||||
_LISTING_CAST_CAP = 20
|
||||
|
||||
|
||||
def _apply_facet_fields(row, meta: dict) -> None:
|
||||
"""Set the filterable/orderable metadata columns shared by plex_items + plex_shows (identical
|
||||
column names) from the cheap section listing: rating, content_rating, studio, release date,
|
||||
@@ -309,14 +314,14 @@ def _apply_facet_fields(row, meta: dict) -> None:
|
||||
# person-page cast filter for names past the top ~20. The watermark gate below (`people_enriched_at
|
||||
# is None`) normally protects this, but it is NOT enough on its own: a migration that resets the
|
||||
# watermark re-opens the regression (0065 nulled it, which is exactly how the S5b fix got undone in
|
||||
# prod). So guard STRUCTURALLY too, by LENGTH: take the fresh listing UNLESS it is shorter than what
|
||||
# is stored. A stored cast longer than the ~20-cap listing can only be an enriched fuller list, so
|
||||
# keep it (robust to any watermark reset); at or below the cap the stored value is itself
|
||||
# listing-sourced, so replacing it with the current listing stays self-correcting when Plex edits
|
||||
# the cast (a pure union never would).
|
||||
# prod). So guard STRUCTURALLY too, by LENGTH against the listing cap: a stored cast LONGER than the
|
||||
# cap can only be an enriched fuller list, so keep it (robust to any watermark reset). A stored cast
|
||||
# at or below the cap is itself listing-sourced, so take the fresh listing — which tracks Plex cast
|
||||
# EDITS in EITHER direction (comparing against this listing's length instead would silently keep a
|
||||
# stale longer cast when Plex TRIMS one).
|
||||
if row.people_enriched_at is None:
|
||||
listing_cast = _tags(meta, "Role", limit=20)
|
||||
if len(row.cast_names or []) <= len(listing_cast or []):
|
||||
listing_cast = _tags(meta, "Role", limit=_LISTING_CAST_CAP)
|
||||
if len(row.cast_names or []) <= _LISTING_CAST_CAP:
|
||||
row.cast_names = listing_cast
|
||||
people = list(dict.fromkeys((row.directors or []) + (row.cast_names or [])))
|
||||
row.people_text = " ".join(people) or None
|
||||
|
||||
Reference in New Issue
Block a user