You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(topics): stop resolved titles being overwritten on scheduler check (#94)
* docs(90): spec placeholder-only self-heal gate for display name
Design for issue #90. The scheduler self-heal overwrites a topic's
stored title with check.DisplayName on any difference, so a noisier
Check title clobbers a good add-time title every poll. Kinozal also
fetches its Check title from the raw mirror host while the rest of the
plugin pins kinozal.tv, producing the divergent title.
Spec: a display_name_is_placeholder provenance flag (migration 0010)
gates self-heal to placeholder-only and locks on first resolve; plus
canonicalize kinozal's Check title host. Lock all existing rows.
* docs(#90): add implementation plan for self-heal gate
* feat(#90): add topics.display_name_is_placeholder column
* feat(#90): flag placeholder vs resolved title at topic creation
* fix(#90): gate display-name self-heal to placeholder titles only
* feat(#90): lock display-name flag on resolve and user rename
* fix(#90): canonicalize kinozal Check title host to p.domain
* docs(#90): document display-name placeholder flag and gated self-heal
Also apply gofmt to domain/domain.go, topics/create.go, and
kinozal/kinozal_test.go (whitespace-only alignment fixes left by
earlier tasks in the series).
Copy file name to clipboardExpand all lines: CLAUDE.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -49,7 +49,7 @@ techdebt/ Debt-tracking files (one per issue, see global rule)
49
49
|`cfsolver`| in-process client to the standalone `cfsolver/` service |
50
50
|`config`| env-driven config struct (caarlos0/env) — **add new env vars here**|
51
51
|`crypto`| AES-256-GCM for tracker credentials and client config blobs |
52
-
| `db` / `db/repo` | pgxpool wrapper + repository structs (`Topics`, `Clients`, `Notifiers`, `Users`, `TrackerCredentials`, `Deliveries`, `Audit`, `Settings`). `Settings` (repo added for the Sonarr integration) reads/writes the singleton `settings` row — `GetSonarr`/`UpsertSonarr` (API key encrypted at rest like `oidc_client_secret_enc`, migration `0009`) + `UpdateSonarrCursor` (history-poll cursor). `Topics` adds `GetByURL(user,url)` for the poller's dedup pre-check; `Users` adds `GetInitialAdmin`. `TrackerCredentials` carries encrypted `secret_enc` (password) **and** `session_enc`/`session_nonce` (cookie-session blob; migration `0002`), plus a nullable `session_expired_at` marker (migration `0003`) the scheduler uses to dedupe expiry notifications (atomic `MarkSessionExpired`, cleared by `SetSession` on re-auth). `Deliveries` (migration `0006`, `topic_deliveries`) records every torrent pushed to a client — `{topic_id, infohash, label, client_id, delivered_at}`, unique on `(topic_id, infohash)` so `Record` is idempotent (`ON CONFLICT DO NOTHING`). `Notifiers` gains `is_default bool` (migration `0008`, per-type unique partial index) + `Update(ctx, id, userID, name, displayName string, events []string, isDefault bool, configEnc, configNonce []byte) error` |
52
+
| `db` / `db/repo` | pgxpool wrapper + repository structs (`Topics`, `Clients`, `Notifiers`, `Users`, `TrackerCredentials`, `Deliveries`, `Audit`, `Settings`). `Settings` (repo added for the Sonarr integration) reads/writes the singleton `settings` row — `GetSonarr`/`UpsertSonarr` (API key encrypted at rest like `oidc_client_secret_enc`, migration `0009`) + `UpdateSonarrCursor` (history-poll cursor). `Topics` adds `GetByURL(user,url)` for the poller's dedup pre-check; `Users` adds `GetInitialAdmin`. `Topics` also carries `display_name_is_placeholder` (migration `0010`): true while the title is a tracker-generated placeholder, set false once resolved (metadata, first self-heal, or a user rename). `UpdateDisplayName` clears it; `Update` clears it only when the submitted name changes. `TrackerCredentials` carries encrypted `secret_enc` (password) **and** `session_enc`/`session_nonce` (cookie-session blob; migration `0002`), plus a nullable `session_expired_at` marker (migration `0003`) the scheduler uses to dedupe expiry notifications (atomic `MarkSessionExpired`, cleared by `SetSession` on re-auth). `Deliveries` (migration `0006`, `topic_deliveries`) records every torrent pushed to a client — `{topic_id, infohash, label, client_id, delivered_at}`, unique on `(topic_id, infohash)` so `Record` is idempotent (`ON CONFLICT DO NOTHING`). `Notifiers` gains `is_default bool` (migration `0008`, per-type unique partial index) + `Update(ctx, id, userID, name, displayName string, events []string, isDefault bool, configEnc, configNonce []byte) error` |
53
53
|**`notify`**| reusable notification dispatcher — `Send(userID, domain.Message)` fans out to all of a user's configured notifiers (best-effort, metered); `SendVia(userID, notifierID, …)` scopes the same fan-out to one notifier when a topic overrides it (nil ⇒ the user's **default** notifiers only (strict; none set ⇒ no send)). Consumers: scheduler new-release alerts + topic-error alerts (both per-topic via `SendVia`; error events deduped to fire once per error episode, on the first failure) + credential session-expiry alerts (global). The single event→notifier fan-out point |
|**`topics`**| shared `BuildAndCreate(store, CreateInput)` — the one tracker-match → `Parse` → fail-open `ResolveMetadata` → build → persist sequence, used by BOTH the `POST /topics` handler and the Sonarr poller (no duplication). Idempotent: a `(user_id,url)` unique-violation returns `Result{Created:false}` instead of erroring. Sentinels `ErrNoTracker`/`ErrParse`/`ErrQualityUnsupported`|
@@ -400,6 +400,8 @@ time so a new topic shows a real name + image immediately instead of a
400
400
for the AddTopic form's preview card. The scheduler additionally self-heals the
401
401
title on each check (`displayNamePersister` → `Topics.UpdateDisplayName`) when a
402
402
plugin's `Check` reports a `DisplayName` that differs from what's stored. The
403
+
self-heal is gated to placeholder titles only (`display_name_is_placeholder`)
404
+
so a noisier `Check` title can never downgrade an already-resolved name (issue #90). The
403
405
image is stored in `topics.image_url` (migration `0005`) and rendered as a
404
406
graceful `<img>` (hidden on load error — no fake fallback).
0 commit comments