Index: scripts/funkwhale-gat.mjs
===================================================================
--- scripts/funkwhale-gat.mjs	(revision aebde2b68e0584c0f2f94fe588ec668858e27513)
+++ scripts/funkwhale-gat.mjs	(revision 7090fd2a91b6ac5a5468359f0b0064888f4107f7)
@@ -52,20 +52,26 @@
 const items = pagina.items || pagina.orderedItems || [];
 if (!items.length) { console.log('geen items in de bibliotheek'); process.exit(1); }
-const a = items[0];
 
-console.log(`bron: ${BRON}\nitem: ${a.name}\n`);
+// ALLE items, niet het eerste. De eerste versie keek naar items[0] en gaf
+// daarover een oordeel alsof het over de bibliotheek ging -- op 16-8 meldde hij
+// zo `track.album` als GAT terwijl drie van de vier tracks hem wel hadden: het
+// ene nummer dat hij toevallig pakte stond niet op een plaat. Een steekproef
+// van een is geen meting, en een meting die je vertrouwt moet zeggen hoeveel.
+console.log(`bron: ${BRON}\nitems: ${items.length}\n`);
+
 let gaten = 0;
-const toon = (lijst, waarde) => {
+const toon = (kop, lijst, kies) => {
+  console.log(kop);
   for (const [naam, test] of lijst) {
-    let ok = false;
-    try { ok = !!test(waarde); } catch { ok = false; }
-    if (!ok) gaten++;
-    console.log(`  ${ok ? 'OK ' : 'GAT'}  ${naam}`);
+    let ok = 0;
+    for (const a of items) { try { if (test(kies(a))) ok++; } catch { /* telt als niet ok */ } }
+    const staat = ok === items.length ? 'OK ' : (ok === 0 ? 'GAT' : 'DEELS');
+    if (ok !== items.length) gaten++;
+    console.log(`  ${staat.padEnd(5)} ${naam}${ok !== items.length ? `  (${ok}/${items.length})` : ''}`);
   }
 };
-console.log('UploadSerializer (regel 1741):');
-toon(UPLOAD, a);
-console.log('\nTrackSerializer (regel 1569 + MusicEntity 1278):');
-toon(TRACK, a.track);
+toon('UploadSerializer (regel 1741):', UPLOAD, (a) => a);
+console.log();
+toon('TrackSerializer (regel 1569 + MusicEntity 1278):', TRACK, (a) => a.track);
 
-console.log(`\n${gaten === 0 ? 'niets meer nodig voor hun ingest' : gaten + ' veld(en) te gaan'}`);
+console.log(`\n${gaten === 0 ? 'niets meer nodig voor hun ingest' : gaten + ' veld(en) nog niet op ELK item'}`);
Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision aebde2b68e0584c0f2f94fe588ec668858e27513)
+++ src/routes/activitypub.js	(revision 7090fd2a91b6ac5a5468359f0b0064888f4107f7)
@@ -1202,5 +1202,5 @@
   const site = publicSite(req.params.slug);
   if (!site) return res.status(404).end();
-  const pl = db.prepare('SELECT id, title, artist, year, cover_url, kind FROM playlists WHERE id = ? AND site_id = ?')
+  const pl = db.prepare('SELECT id, title, artist, year, cover_url, kind, release_date, mb_release_id, created_at FROM playlists WHERE id = ? AND site_id = ?')
     .get(req.params.id, site.id);
   if (!pl) return res.status(404).end();
Index: src/services/PlaylistService.js
===================================================================
--- src/services/PlaylistService.js	(revision aebde2b68e0584c0f2f94fe588ec668858e27513)
+++ src/services/PlaylistService.js	(revision 7090fd2a91b6ac5a5468359f0b0064888f4107f7)
@@ -155,4 +155,8 @@
       cover: p.cover_url || fallbackCover,
       kind: (p.kind === 'playlist') ? 'playlist' : 'album',
+      // created_at hoort erbij omdat de AP-kant er `published` van maakt. Zonder
+      // dit veld viel buildAlbumObject terug op 1970, en dat stond op 16-8
+      // gewoon op de federatie.
+      created_at: p.created_at,
       // Leeg als het een afspeellijst is -- de opslag houdt ze daar al leeg,
       // maar dit is de plek waar de editor leest en die mag niet afhangen van
Index: src/services/music/index.js
===================================================================
--- src/services/music/index.js	(revision aebde2b68e0584c0f2f94fe588ec668858e27513)
+++ src/services/music/index.js	(revision 7090fd2a91b6ac5a5468359f0b0064888f4107f7)
@@ -451,4 +451,19 @@
   const uit = new Map();
   for (const r of rijen) if (!uit.has(r.tid)) uit.set(r.tid, r);
+  // De post die deze plaat uitbrengt, EEN keer per album opgezocht en niet per
+  // track: uitgavePost() doet er echt werk voor (hij leest de typering van de
+  // post) en een site heeft veel meer nummers dan platen.
+  //
+  // WAAROM DIT ERBIJ MOET: buildPlaylistCollection laat leenVanPost de naam van
+  // de post overnemen -- de post IS de uitgave. Zonder dezelfde lening hier zou
+  // het ingesloten Album "Cartoon Epic" heten en zijn eigen URI "Geen koffie,
+  // wel thee!". Een id met twee namen, en dat is precies wat op 16-8 uit de
+  // meting rolde.
+  const perAlbum = new Map();
+  for (const r of uit.values()) {
+    if (perAlbum.has(r.id)) continue;
+    perAlbum.set(r.id, uitgavePost(siteId, r.id));
+  }
+  for (const r of uit.values()) r._post = perAlbum.get(r.id) || null;
   return uit;
 }
@@ -470,13 +485,23 @@
   if (!pl) return null;
   const abs = (u) => !u ? null : (/^https?:/i.test(u) ? u : `${base}${u.startsWith('/') ? '' : '/'}${u}`);
-  const wanneer = pl.created_at ? new Date(pl.created_at).toISOString() : new Date(0).toISOString();
+  // GEEN epoch als terugval. `published` is bij hen verplicht, maar 1970 is een
+  // ANTWOORD en geen ontbrekend veld -- en dat is erger: een lezer kan een gat
+  // opmerken, een leugen niet. playlists.created_at heeft een default, dus als
+  // hij hier ontbreekt is er een leespad dat de kolom laat vallen. Dat willen we
+  // zien, niet maskeren. (Zo kwam op 16-8 de route boven water die id, title,
+  // artist, year, cover_url en kind selecteerde en de rest niet.)
+  const wanneer = pl.created_at ? new Date(pl.created_at).toISOString() : null;
+  // Dezelfde lening als in buildPlaylistCollection: de post die de plaat
+  // uitbrengt geeft zijn titel, en de eigen titel blijft als alsoKnownAs staan.
+  const titel = (pl._post && pl._post.title) || pl.title;
   const album = {
     type: 'Album',
     id: `${actorId(base, site.slug)}/playlists/${pl.id}`,
-    name: pl.title,
-    published: wanneer,
+    name: titel,
+    ...(wanneer ? { published: wanneer } : {}),
     attributedTo: actorId(base, site.slug),
-    artist_credit: artistCredit(base, site, pl.artist, wanneer),
+    artist_credit: artistCredit(base, site, pl.artist, wanneer || new Date().toISOString()),
   };
+  if (titel !== pl.title) album.alsoKnownAs = pl.title;
   // `released` alleen als er een ECHTE datum is. `year` vult hem niet aan: een
   // jaartal is geen dag, en dat is de reden dat release_date bestaat.
