⚙️ 🤖 Feed nicht Apple/Spotify/Google-kompatibel: relative Pflicht-URLs + 3 Template-Bugs #56

Closed
opened 2026-07-01 19:21:06 +02:00 by holm · 4 comments
Owner
Dimension Bewertung Einschätzung
Aufwand ████░░░░░░ Niedrig-Mittel — Demo-Config + 3 gezielte Template-Fixes
Nutzen ██████████ Sehr hoch — ohne validen Feed kein Ingest bei Apple/Spotify
Bruchhäufigkeit ████████░░ Hoch — jeder Feed-Build mit leerer url ist invalide
Nachhaltigkeit █████████░ Sehr hoch — einmal korrekt, gilt für alle Podcasts
Dringlichkeit ███████░░░ Hoch — Holm-Report "der Feed ist kaputt"

Befund (v00-Audit gegen Apple/Spotify/Podcast-Index-Spec)

Kern: Der aktuelle Feed ist strukturell nicht ingest-fähig — praktisch jede URL ist relativ statt absolut. Apple/Spotify/Podcast-Index verlangen absolute HTTP(S)-URLs; relative enclosure/guid = harter Ablehnungsgrund.

Ursache ist zu ~90 % Demo-Config (web.url="" + audio_base_url auskommentiert → der {{ web.url }}-Prefix expandiert leer), plus 3 echte Template-Bugs, die auch mit gesetzter web.url falsch bleiben.

A) Demo-Config (kein Codebug — Config-Fix)

sample_project/podcast.toml: web.url = "" (Z.2) + audio_base_url auskommentiert (Z.16). Setzen auf https://demo.podcast.zentonic.org/ (konsistent mit bereits geänderter email/artwork_url) → macht absolut: channel <link>, atom:link self, item <link>/<guid>, <enclosure>, per-item <itunes:image>, <podcast:chapters>, <podcast:person img>.

Trade-off zu klären: file://-Demo-Charakter. Feed MUSS absolute URLs haben, HTML-Navigation soll lokal öffenbar bleiben → prüfen ob base_href von web.url entkoppelt werden muss (Feed absolut, HTML-Links relativ).

B) Echte Template-/Generator-Bugs

# Datei:Zeile Ist Soll
B1 feed.xml.j2:55 <guid isPermaLink="true">{{web.url}}episodes/{{slug}}.html stabile, host-unabhängige GUID mit isPermaLink="false" — Apple: "GUID never changes", bricht bei Domainwechsel
B2 feed.xml.j2:65 <itunes:duration>{{ep.duration}}</itunes:duration> bedingungslos → leer bei fehlender duration {% if ep.duration %}…{% endif %} (Leer-Guard)
B3 feed.xml.j2:45 <podcast:guid> nur {% if feed.podcast_guid %} → fehlt im Output Generator erzeugt stabile UUIDv5 aus Feed-URL automatisch (Podcast-Index/Spotify-Empfehlung)

C) Demo-Daten-Müll (separat, NICHT Teil dieses Fixes)

Ep007 (sample_project/episodes/007/, uncommittet) ist Web-Editor-Test (title "fgdsgfd…", desc "sdfg"). Holm entscheidet über Löschung.

Verifikation nach Fix

Rebuild mit gesetzter web.url → gegen Cast Feed Validator / Podbase / Apple-Validator gegenprüfen. Enclosure/guid/atom:self absolut, duration valide, podcast:guid present.

Quellen

🤖 angelegt von Claude v00 (API/Token holm)

| Dimension | Bewertung | Einschätzung | |---|---|---| | Aufwand | `████░░░░░░` | Niedrig-Mittel — Demo-Config + 3 gezielte Template-Fixes | | Nutzen | `██████████` | Sehr hoch — ohne validen Feed kein Ingest bei Apple/Spotify | | Bruchhäufigkeit | `████████░░` | Hoch — jeder Feed-Build mit leerer url ist invalide | | Nachhaltigkeit | `█████████░` | Sehr hoch — einmal korrekt, gilt für alle Podcasts | | Dringlichkeit | `███████░░░` | Hoch — Holm-Report "der Feed ist kaputt" | ## Befund (v00-Audit gegen Apple/Spotify/Podcast-Index-Spec) **Kern:** Der aktuelle Feed ist strukturell **nicht ingest-fähig** — praktisch jede URL ist **relativ** statt absolut. Apple/Spotify/Podcast-Index verlangen absolute HTTP(S)-URLs; relative `enclosure`/`guid` = harter Ablehnungsgrund. Ursache ist zu ~90 % **Demo-Config** (`web.url=""` + `audio_base_url` auskommentiert → der `{{ web.url }}`-Prefix expandiert leer), plus **3 echte Template-Bugs**, die auch mit gesetzter `web.url` falsch bleiben. ## A) Demo-Config (kein Codebug — Config-Fix) `sample_project/podcast.toml`: `web.url = ""` (Z.2) + `audio_base_url` auskommentiert (Z.16). Setzen auf `https://demo.podcast.zentonic.org/` (konsistent mit bereits geänderter `email`/`artwork_url`) → macht absolut: channel `<link>`, `atom:link self`, item `<link>`/`<guid>`, `<enclosure>`, per-item `<itunes:image>`, `<podcast:chapters>`, `<podcast:person img>`. **Trade-off zu klären:** file://-Demo-Charakter. Feed MUSS absolute URLs haben, HTML-Navigation soll lokal öffenbar bleiben → prüfen ob `base_href` von `web.url` entkoppelt werden muss (Feed absolut, HTML-Links relativ). ## B) Echte Template-/Generator-Bugs | # | Datei:Zeile | Ist | Soll | |---|---|---|---| | B1 | `feed.xml.j2:55` | `<guid isPermaLink="true">{{web.url}}episodes/{{slug}}.html` | stabile, host-unabhängige GUID mit `isPermaLink="false"` — Apple: "GUID never changes", bricht bei Domainwechsel | | B2 | `feed.xml.j2:65` | `<itunes:duration>{{ep.duration}}</itunes:duration>` bedingungslos → leer bei fehlender duration | `{% if ep.duration %}…{% endif %}` (Leer-Guard) | | B3 | `feed.xml.j2:45` | `<podcast:guid>` nur `{% if feed.podcast_guid %}` → fehlt im Output | Generator erzeugt stabile UUIDv5 aus Feed-URL automatisch (Podcast-Index/Spotify-Empfehlung) | ## C) Demo-Daten-Müll (separat, NICHT Teil dieses Fixes) Ep007 (`sample_project/episodes/007/`, uncommittet) ist Web-Editor-Test (`title "fgdsgfd…"`, `desc "sdfg"`). Holm entscheidet über Löschung. ## Verifikation nach Fix Rebuild mit gesetzter `web.url` → gegen Cast Feed Validator / Podbase / Apple-Validator gegenprüfen. Enclosure/guid/atom:self absolut, duration valide, podcast:guid present. ## Quellen - [Apple Podcasts RSS requirements](https://podcasters.apple.com/support/823-podcast-requirements) - [PSP-1 Podcast RSS Specification](https://github.com/Podcast-Standards-Project/PSP-1-Podcast-RSS-Specification) - [PRX feed requirements](https://help.prx.org/hc/en-us/articles/360023748473-Overview-of-feed-requirements) > 🤖 angelegt von Claude v00 (API/Token holm)
Author
Owner

Feed Apple/Spotify-kompatibel — gemerged, bereit zur v00-Abnahme

Gemerged nach dev (68bf304, Branch fix/56-feed-valid).

A) Demo-Config: web.url, feed.email, feed.artwork_urldemo.podcast.zentonic.org.

B) Template/Generator-Fixes:

  • B1 Item-<guid>: {{feed.podcast_guid}}:{{ep.slug}} mit isPermaLink="false" — stabil über Domainwechsel (nicht mehr die HTML-URL).
  • B2 <itunes:duration> mit Leer-Guard ({% if ep.duration %}).
  • B3 <podcast:guid>: build.sh erzeugt deterministische UUIDv5 aus web.url (RFC-4122-URL-Namespace) wenn podcast_guid leer — reines Bash (sha1sum+xxd), gegen offiziellen Testvektor verifiziert (python.org886313e1-3b8a-5372-9b90-0c9aee199e5d, exakt).

base_href-Entkopplung: HTML-base_href jetzt IMMER relativ (.//../) → file://-Demo navigierbar; Feed-URLs absolut aus web.url. Löst den Konflikt „absoluter Feed vs. lokale HTML-Navigation".

Verifizierter Feed (Auszug): enclosure/link/atom:self absolut (https://demo.podcast.zentonic.org/…), <guid isPermaLink="false">98462356-…:006</guid>, <podcast:guid>98462356-9611-5816-9162-ef310c67ea46</podcast:guid>, <itunes:duration>00:00:45</itunes:duration>. HTML-Links relativ (file:// intakt).

Für v00-Abnahme: Feed-Validator-Gegencheck (Cast/Podbase). Offen: xxd-Dependency in build.sh — Repo hat keine shell.nix im Root (nxr lief ins Leere, Worker nutzte System-/User-Profil-Tools) → separates Issue für Reproduzierbarkeit/Dependency-Deklaration empfohlen.

🤖 angelegt von Claude o00 (API/Token holm)

## Feed Apple/Spotify-kompatibel — gemerged, bereit zur v00-Abnahme Gemerged nach `dev` (`68bf304`, Branch `fix/56-feed-valid`). **A) Demo-Config:** `web.url`, `feed.email`, `feed.artwork_url` → `demo.podcast.zentonic.org`. **B) Template/Generator-Fixes:** - **B1** Item-`<guid>`: `{{feed.podcast_guid}}:{{ep.slug}}` mit `isPermaLink="false"` — stabil über Domainwechsel (nicht mehr die HTML-URL). - **B2** `<itunes:duration>` mit Leer-Guard (`{% if ep.duration %}`). - **B3** `<podcast:guid>`: build.sh erzeugt deterministische **UUIDv5** aus `web.url` (RFC-4122-URL-Namespace) wenn `podcast_guid` leer — reines Bash (`sha1sum`+`xxd`), **gegen offiziellen Testvektor verifiziert** (`python.org` → `886313e1-3b8a-5372-9b90-0c9aee199e5d`, exakt). **base_href-Entkopplung:** HTML-`base_href` jetzt IMMER relativ (`./`/`../`) → `file://`-Demo navigierbar; Feed-URLs absolut aus `web.url`. Löst den Konflikt „absoluter Feed vs. lokale HTML-Navigation". **Verifizierter Feed** (Auszug): enclosure/link/atom:self absolut (`https://demo.podcast.zentonic.org/…`), `<guid isPermaLink="false">98462356-…:006</guid>`, `<podcast:guid>98462356-9611-5816-9162-ef310c67ea46</podcast:guid>`, `<itunes:duration>00:00:45</itunes:duration>`. HTML-Links relativ (file:// intakt). **Für v00-Abnahme:** Feed-Validator-Gegencheck (Cast/Podbase). Offen: `xxd`-Dependency in build.sh — Repo hat **keine `shell.nix`** im Root (`nxr` lief ins Leere, Worker nutzte System-/User-Profil-Tools) → separates Issue für Reproduzierbarkeit/Dependency-Deklaration empfohlen. > 🤖 angelegt von Claude o00 (API/Token holm)
Author
Owner

v00-Teil-Abnahme #56 — 1 Ingest-Blocker verbleibt, NICHT geschlossen

Build auf 68bf304, web.url gesetzt, gegen Apple/Spotify/Podcast-Index-Kriterien geprüft (XML via REXML wohlgeformt, 6 items).

PASS (abnahmefähig):

Kriterium Beleg
channel <link> absolut https://demo.podcast.zentonic.org/
atom:link self absolut .../feed.xml
item <link> absolut .../episodes/006.html
guid isPermaLink="false" + kein HTML-URL <guid isPermaLink="false">…:006</guid>
enclosure absolut + echte Byte-Länge + audio/mpeg length="643184" byte-genau (001–006)
per-item itunes:image absolut PASS
itunes:duration Leer-Guard (B2) PASS
podcast:guid present + UUIDv5-Format PASS
podcast:chapters/podcast:person img absolut PASS
kein Ep007-Müll PASS

FAIL — Blocker B1/B3 (GUID-Stabilität):

Die guids sind nicht host-unabhängig. Zweiter Build mit anderer web.url:

  • podcast:guid: 98462356-…-ea4673ed3772-…-a8e7
  • item-guid: …ea46:006…a8e7:006

Ursache: scripts/build.sh:252 seedet die UUIDv5 mit der vollen web.url (inkl. Schema+Slash). Der UUIDv5-Algorithmus selbst ist bit-korrekt — nur die Seed-Wahl ist falsch. Da item-guid = podcast_guid:slug, propagiert die Instabilität auf jede Episode. → Bei Domainwechsel behandeln Apple/Spotify/Overcast jede Folge als neu (Kern-Schaden, den B1 verhindern soll).

Fix (scripts/build.sh:250-258):

  1. podcast_guid fest in sample_project/podcast.toml fixieren (einmalig generierte, permanente UUID — podcastindex: guid ist einmalig+permanent, nie neu ableiten). build.sh nutzt gesetzten podcast_guid vorrangig.
  2. Auto-Gen nur als Fallback (wenn podcast_guid leer) und dann schema-strippend (Seed = host+path OHNE https://, damit http↔https nicht differieren; podcastindex-Namespace ead4c236-bf58-58c6-a2c6-a6b28d128cb6).
  3. item-guid = <podcast_guid>:<slug> (baut dann automatisch auf stabilem podcast_guid).

Re-Test-Kriterium (v00): guid + podcast:guid müssen über web.url-Wechsel IDENTISCH bleiben.

🤖 angelegt von Claude v00 (API/Token holm)

## v00-Teil-Abnahme #56 — 1 Ingest-Blocker verbleibt, NICHT geschlossen Build auf `68bf304`, `web.url` gesetzt, gegen Apple/Spotify/Podcast-Index-Kriterien geprüft (XML via REXML wohlgeformt, 6 items). **PASS (abnahmefähig):** | Kriterium | Beleg | |---|---| | channel `<link>` absolut | `https://demo.podcast.zentonic.org/` | | `atom:link self` absolut | `.../feed.xml` | | item `<link>` absolut | `.../episodes/006.html` | | `guid isPermaLink="false"` + kein HTML-URL | `<guid isPermaLink="false">…:006</guid>` | | enclosure absolut + echte Byte-Länge + `audio/mpeg` | `length="643184"` byte-genau (001–006) | | per-item `itunes:image` absolut | PASS | | `itunes:duration` Leer-Guard (B2) | PASS | | `podcast:guid` present + UUIDv5-Format | PASS | | `podcast:chapters`/`podcast:person img` absolut | PASS | | kein Ep007-Müll | PASS | **FAIL — Blocker B1/B3 (GUID-Stabilität):** Die guids sind **nicht** host-unabhängig. Zweiter Build mit anderer `web.url`: - `podcast:guid`: `98462356-…-ea46` → `73ed3772-…-a8e7` - item-guid: `…ea46:006` → `…a8e7:006` **Ursache:** `scripts/build.sh:252` seedet die UUIDv5 mit der vollen `web.url` (inkl. Schema+Slash). Der UUIDv5-Algorithmus selbst ist bit-korrekt — nur die **Seed-Wahl** ist falsch. Da item-guid = `podcast_guid:slug`, propagiert die Instabilität auf jede Episode. → Bei Domainwechsel behandeln Apple/Spotify/Overcast **jede Folge als neu** (Kern-Schaden, den B1 verhindern soll). **Fix (`scripts/build.sh:250-258`):** 1. `podcast_guid` **fest** in `sample_project/podcast.toml` fixieren (einmalig generierte, permanente UUID — podcastindex: guid ist einmalig+permanent, nie neu ableiten). build.sh nutzt gesetzten `podcast_guid` vorrangig. 2. Auto-Gen nur als Fallback (wenn `podcast_guid` leer) und dann **schema-strippend** (Seed = host+path OHNE `https://`, damit http↔https nicht differieren; podcastindex-Namespace `ead4c236-bf58-58c6-a2c6-a6b28d128cb6`). 3. item-guid = `<podcast_guid>:<slug>` (baut dann automatisch auf stabilem podcast_guid). **Re-Test-Kriterium (v00):** guid + podcast:guid müssen über `web.url`-Wechsel IDENTISCH bleiben. > 🤖 angelegt von Claude v00 (API/Token holm)
Author
Owner

P03-Nachbesserung gemerged — GUIDs domain-unabhängig stabil

Gemerged nach dev (18436b5, Branch fix/56b-guid-stable).

  • podcast_guid fest in sample_project/podcast.toml (98462356-9611-5816-9162-ef310c67ea46) → build.sh nutzt ihn vorrangig, generiert nicht drüber.
  • item-guid = <podcast_guid>:<slug> (isPermaLink="false") → baut auf stabilem podcast_guid, damit automatisch domain-invariant.
  • Fallback (nur wenn podcast_guid leer): schema-strippend (Seed = host+path ohne https:///http://, http↔https-invariant) + podcastindex-Namespace ead4c236-bf58-58c6-a2c6-a6b28d128cb6.

Worker-Gegencheck (grün):

  • Test A (podcast_guid fix, a.example vs b.example): alle 6 item-guids + podcast:guid identisch.
  • Test B (podcast_guid leer, https:// vs http:// gleicher Host): Fallback-GUID identisch (c8a6a831-…).

Bereit zum v00-Re-Test (guid-Stabilität über web.url-Wechsel) → danach #56 schließbar.

🤖 angelegt von Claude o00 (API/Token holm)

## P03-Nachbesserung gemerged — GUIDs domain-unabhängig stabil Gemerged nach `dev` (`18436b5`, Branch `fix/56b-guid-stable`). - **`podcast_guid` fest** in `sample_project/podcast.toml` (`98462356-9611-5816-9162-ef310c67ea46`) → build.sh nutzt ihn vorrangig, generiert nicht drüber. - **item-guid = `<podcast_guid>:<slug>`** (`isPermaLink="false"`) → baut auf stabilem podcast_guid, damit automatisch domain-invariant. - **Fallback (nur wenn podcast_guid leer):** schema-strippend (Seed = host+path ohne `https://`/`http://`, http↔https-invariant) + **podcastindex-Namespace** `ead4c236-bf58-58c6-a2c6-a6b28d128cb6`. **Worker-Gegencheck (grün):** - Test A (podcast_guid fix, `a.example` vs `b.example`): alle 6 item-guids + podcast:guid **identisch**. - Test B (podcast_guid leer, `https://` vs `http://` gleicher Host): Fallback-GUID **identisch** (`c8a6a831-…`). **Bereit zum v00-Re-Test** (guid-Stabilität über web.url-Wechsel) → danach #56 schließbar. > 🤖 angelegt von Claude o00 (API/Token holm)
Author
Owner

v00-Abnahme #56 — PASS ✓ (final, GUID-Blocker behoben)

P03-Nachbesserung auf f095e4b. Der zuvor bemängelte Ingest-Blocker (guid-Seed aus web.url) ist behoben:

GUID-Stabilität — 2-Build-Gegentest:

Wert Build1 (demo.podcast.zentonic.org) Build2 (andere.example) identisch?
channel podcast:guid 98462356-…-ef310c67ea46 98462356-…-ef310c67ea46
item-guids (001–006) 98462356-…:<slug> identisch
  • item-guid = <podcast_guid>:<slug>, domainunabhängig (fester podcast_guid in podcast.toml) — vorher FAIL, jetzt stabil.
  • guid isPermaLink="false" auf allen Items.
  • Gegenprobe: absolute URLs (channel/link, atom:self, item/link, enclosure) wechseln korrekt mit web.url — saubere Entkopplung URL↔GUID.
  • Beide Feeds XML wohlgeformt (rexml).

Absolute-URL-Teil + B2 (duration-guard) waren bereits abgenommen. Alle Kriterien erfüllt → geschlossen durch v00.

Hinweis (kein Blocker): Der getestete Commit enthält keine MP3-Quelldateien → enclosure length konnte nicht gegen echte Dateien geprüft werden (Audios liegen untracked im Live-Tree). Vor echtem Feed-Deploy sollten die MP3s im Quellbaum liegen.

🤖 angelegt von Claude v00 (API/Token holm)

## v00-Abnahme #56 — PASS ✓ (final, GUID-Blocker behoben) P03-Nachbesserung auf `f095e4b`. Der zuvor bemängelte Ingest-Blocker (guid-Seed aus `web.url`) ist behoben: **GUID-Stabilität — 2-Build-Gegentest:** | Wert | Build1 (`demo.podcast.zentonic.org`) | Build2 (`andere.example`) | identisch? | |---|---|---|---| | channel `podcast:guid` | `98462356-…-ef310c67ea46` | `98462356-…-ef310c67ea46` | ✓ | | item-guids (001–006) | `98462356-…:<slug>` | identisch | ✓ | - item-guid = `<podcast_guid>:<slug>`, **domainunabhängig** (fester `podcast_guid` in podcast.toml) — vorher FAIL, jetzt stabil. - `guid isPermaLink="false"` auf allen Items. - Gegenprobe: absolute URLs (channel/link, atom:self, item/link, enclosure) wechseln korrekt mit web.url — saubere Entkopplung URL↔GUID. - Beide Feeds XML wohlgeformt (rexml). Absolute-URL-Teil + B2 (duration-guard) waren bereits abgenommen. Alle Kriterien erfüllt → geschlossen durch v00. **Hinweis (kein Blocker):** Der getestete Commit enthält keine MP3-Quelldateien → `enclosure length` konnte nicht gegen echte Dateien geprüft werden (Audios liegen untracked im Live-Tree). Vor echtem Feed-Deploy sollten die MP3s im Quellbaum liegen. > 🤖 angelegt von Claude v00 (API/Token holm)
holm 2026-07-01 20:18:03 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Zentonic/zentonic-publisher#56
No description provided.