Changeset 19430fa in Klonkt


Ignore:
Timestamp:
08/14/2026 12:40:43 AM (4 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
5a49eba
Parents:
919b82d
Message:

GUID's blijven altijd behouden bij een verhuizing

Robin vroeg of we bestaande GUID's hergebruiken. Het antwoord was zes vakjes:

zip ophaalknop

posts nieuw bij andere origin altijd nieuw
tracks OUD BEHOUDEN altijd nieuw
playlists OUD BEHOUDEN altijd nieuw

Vier keer nieuw, twee keer niet, en die twee uitzonderingen waren niet
besloten maar ontstaan: de zip schreef INSERT OR REPLACE met het id uit het
archief zonder dat daar ooit over nagedacht is. Die scheve tabel was precies de
oorzaak van de shortcode die Robin op TikTik zag: post uit de zip met
[[track:oud]], nummer uit de pull met een nieuw id.

Zijn besluit: altijd behouden. Nu is het één regel.

WAAROM DAT MAG. Het interne id is niet de AP-URI. https://nieuw/ap/notes/<id>
is een ander adres dan https://oud/ap/notes/<id>, dus je claimt niets van een
ander door het GUID te hergebruiken. Het oude argument in de code ("een id op
andermans domein publiceren is een vervalsingsoppervlak") haalde die twee door
elkaar. Wat je wint: elke interne verwijzing blijft kloppen, [[track:]],
[[playlist:]] en [[album:]] wijzen na de verhuizing nog naar het goede ding.

Wat NIET verandert is de AP-URI. Die is domeingebonden en hoort nieuw te zijn,
en daar is de migration-collectie voor. idsBehouden gaat voortaan alleen daar
nog over.

Gemeten door dezelfde inhoud via BEIDE routes over elkaar heen te halen:

posts oud 6 | nieuw 6 | zelfde id 6 | afwijkend 0
audio_tracks oud 3 | nieuw 3 | zelfde id 3 | afwijkend 0
playlists oud 1 | nieuw 1 | zelfde id 1 | afwijkend 0

Geen dubbele. Zip en ophaalknop zijn daarmee inwisselbaar geworden, en dat was
eerder de combinatie die stukging.

Changed files:
src/services/ArchiveImportService.js

  • posts houden hun id, ongeacht de origin
  • de waarschuwing zegt nu wat er echt verandert: het AP-adres, niet het id

src/services/MigrationService.js

  • posts, tracks en playlists nemen het id van de bron over
  • "staat hij hier al" is daarmee een blik in de tabel in plaats van een omweg via ap_migration; verwijderen en opnieuw ophalen werkt vanzelf
  • de eerderPl/eerder-omwegen konden weg

test/archive-import.test.js

  • de origin-test omgedraaid: het AP-adres verandert, het id blijft

test/fep1580-migration.test.js

  • de shortcode-test toetst nu de UITKOMST (wijst naar een bestaand nummer) in plaats van de route ernaartoe
  • nieuwe test voor het botsingsgeval, waar het bijtrekken wel nodig is

remarks: het bijtrekken van [[track:]] blijft bestaan als vangnet voor een
botsend id. In het normale geval doet het niets meer, en dat is de bedoeling.
Suite 991 groen.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

Files:
4 edited

Legend:

Unmodified
Added
Removed
  • src/services/ArchiveImportService.js

    r919b82d r19430fa  
    285285  rapport.origin = manifest.origin || null;
    286286
    287   // IDENTITEIT. Gelijke origin -> de AP-ids blijven, en daarmee vinden de boosts
    288   // en antwoorden die er al naar wijzen hun post terug. Anders nieuwe ids, want
    289   // een id op andermans domein publiceren is een vervalsingsoppervlak en andere
    290   // servers halen het daar toch op.
     287  // IDENTITEIT. Het INTERNE id blijft altijd (Robins besluit, 14-8).
     288  //
     289  // Dat is iets anders dan de AP-URI. Die is domeingebonden en wordt hoe dan
     290  // ook nieuw: https://nieuw/ap/notes/<id> is een ander adres dan
     291  // https://oud/ap/notes/<id>. Je claimt dus niets van een ander door het GUID
     292  // te hergebruiken, en je wint dat elke INTERNE verwijzing blijft kloppen:
     293  // [[track:]], [[playlist:]] en [[album:]] wijzen na een verhuizing nog naar
     294  // het goede ding.
     295  //
     296  // Voorheen hing dit aan de origin, en alleen voor posts; tracks en playlists
     297  // hielden hun id al wel. Die scheve tabel was precies waarom een post uit de
     298  // zip met [[track:oud]] naast een nummer uit de pull met een nieuw id kwam te
     299  // staan, en je de shorthand als kale tekst in je bericht zag.
     300  //
     301  // `idsBehouden` gaat hieronder alleen nog over de AP-URI: gelijke origin
     302  // betekent dat ook die identiek blijft, en dan valt er niets te vertalen.
    291303  const idsBehouden = !!(manifest.origin && eigenOrigin && manifest.origin === eigenOrigin);
    292304  rapport.idsBehouden = idsBehouden;
    293305  if (!idsBehouden) {
    294306    rapport.waarschuwingen.push(
    295       `origin verschilt (archief ${manifest.origin || '?'} vs deze site ${eigenOrigin || '?'}): nieuwe AP-ids, de oude blijven als verwijzing staan`,
     307      `origin verschilt (archief ${manifest.origin || '?'} vs deze site ${eigenOrigin || '?'}): de berichten krijgen een nieuw AP-adres. Hun interne id blijft, dus verwijzingen binnen je site blijven kloppen.`,
    296308    );
    297309  }
     
    307319    const o = JSON.parse(files.get(pad).toString('utf8'));
    308320    const oudId = decodeURIComponent(String(o.id || '').split('/ap/notes/')[1] || path.basename(pad, '.json'));
    309     const nieuwId = idsBehouden ? oudId : randomUUID();
     321    // Altijd het id uit het archief. Staat er hier al iets met dat id, dan is
     322    // dat hetzelfde object, en dat handelt de botsingscontrole hieronder af.
     323    const nieuwId = oudId;
    310324    idKaart.set(oudId, nieuwId);
    311325
  • src/services/MigrationService.js

    r919b82d r19430fa  
    417417        if (auteur && auteur !== bronActor.id) continue;              // alleen wat van HEM was
    418418        gezien++;
    419         // Al binnen? Alleen overslaan als het bericht er OOK nog staat. Heb je
    420         // het verwijderd, dan is opnieuw ophalen precies wat je bedoelt, en
    421         // een mapping die dat blokkeert is een val: opruimen hielp dan niet,
    422         // want de blokkade zat in ap_migration en niet in de posts.
    423         const eerderPost = migrationTarget(site.slug, o.id);
    424         if (eerderPost) {
    425           const postId = String(eerderPost).split('/').pop();
    426           if (db.prepare('SELECT 1 FROM posts WHERE id = ? AND site_id = ?').get(decodeURIComponent(postId), site.id)) {
    427             rapport.overgeslagen++;
    428             continue;
    429           }
    430           rapport.opnieuw++;   // weg hier, dus opnieuw binnenhalen
    431         }
     419        // Het interne id BLIJFT (Robins besluit, 14-8). Daarmee is "staat hij
     420        // hier al" gewoon een blik in de tabel, en niet iets dat je uit een
     421        // aparte mapping moet afleiden. Verwijder je een bericht en haal je
     422        // opnieuw op, dan komt het gewoon terug: er staat immers niets meer.
     423        const id = ruwId(o.id) || crypto.randomUUID();
     424        if (db.prepare('SELECT 1 FROM posts WHERE id = ? AND site_id = ?').get(id, site.id)) {
     425          rapport.overgeslagen++;
     426          continue;
     427        }
     428        if (migrationTarget(site.slug, o.id)) rapport.opnieuw++;   // was er, is weg, komt terug
    432429
    433430        // Media eerst, want een post die naar een plaatje wijst dat we niet
     
    451448        }
    452449
    453         const id = crypto.randomUUID();
    454450        const cover = binnen.find((b) => AFBEELDING.test(b.type || ''));
    455451        const rest = binnen.filter((b) => b !== cover);
     
    526522        // Alleen LEGE velden worden gevuld. Wat jij zelf hebt aangepast blijft
    527523        // staan; een migratie hoort je correcties niet terug te draaien.
    528         const eerder = migrationTarget(site.slug, a.id);
    529         if (eerder) {
    530           const lokaalId = String(eerder).split('/').pop();
     524        const trackId = ruwId(a.id) || crypto.randomUUID();
     525        {
    531526          const rij = db.prepare('SELECT id, cover_url, duration, artist FROM audio_tracks WHERE id = ? AND site_id = ?')
    532             .get(lokaalId, site.id);
     527            .get(trackId, site.id);
    533528          if (rij) {
    534529            trackKaart.set(String(a.id), rij.id);   // MOET, anders vinden de playlists hem niet
     
    557552            continue;
    558553          }
    559           // De rij is weg maar de mapping staat er nog. Dan is opnieuw ophalen
    560           // precies wat je wilt, dus we vallen door naar de gewone tak.
    561554        }
    562555        const bron = a.url && (typeof a.url === 'string' ? a.url : (Array.isArray(a.url) ? (a.url[0] && (a.url[0].href || a.url[0])) : a.url.href));
     
    583576          else rapport.waarschuwingen.push(`hoes niet opgehaald: ${a.name || hoesUrl}`);
    584577        }
    585         const trackId = crypto.randomUUID();
    586578        const mediaId = crypto.randomUUID();
    587579        try {
     
    651643          continue;
    652644        }
    653         // Bestond hij al? Dan dezelfde rij bijwerken. Zonder deze stap levert
    654         // elke tweede ronde een dubbele plaat op.
    655         const eerderPl = migrationTarget(site.slug, uri);
    656645        // De hoes van de plaat, net als bij een nummer.
    657646        let plHoes = null;
     
    665654          else rapport.waarschuwingen.push(`hoes van playlist niet opgehaald: ${plc.name || uri}`);
    666655        }
    667         const plId = (() => {
    668           if (!eerderPl) return crypto.randomUUID();
    669           const bestaand = String(eerderPl).split('/').pop();
    670           return db.prepare('SELECT 1 FROM playlists WHERE id = ? AND site_id = ?').get(bestaand, site.id)
    671             ? bestaand : crypto.randomUUID();
    672         })();
     656        // Ook hier het id van de bron. Dan blijft [[playlist:<id>]] in een
     657        // bericht wijzen, en is een tweede ronde vanzelf dezelfde rij.
     658        const plId = ruwId(uri) || crypto.randomUUID();
    673659        try {
    674660          db.prepare(`INSERT INTO playlists (id, site_id, title, artist, year, kind, cover_url) VALUES (?,?,?,?,?,?,?)
  • test/archive-import.test.js

    r919b82d r19430fa  
    126126});
    127127
    128 test('een andere origin levert NIEUWE ids op, met een waarschuwing', () => {
    129   // Oude ids houden op een ander domein zou objecten publiceren onder een id dat
    130   // je niet beheert -- andere servers halen dat daar op, en het is bovendien een
    131   // vervalsingsoppervlak.
     128test('een andere origin geeft een nieuw AP-adres, maar het interne id BLIJFT', () => {
     129  // Omgedraaid op 14-8 (Robin: "altijd behouden"). Het interne id is niet
     130  // hetzelfde als de AP-URI: https://nieuw/ap/notes/<id> is een ander adres dan
     131  // https://oud/ap/notes/<id>, dus je claimt niets van een ander door het GUID
     132  // te hergebruiken. Wat je wint is dat elke INTERNE verwijzing blijft kloppen.
     133  //
     134  // Het oude gedrag brak precies dat: een post uit de zip hield
     135  // [[track:<oud id>]] in zijn tekst terwijl het nummer een nieuw id kreeg, en
     136  // dan zie je de shorthand als kale tekst in je bericht staan.
    132137  leeg();
    133138  const files = new Map(ARCHIEF.files);
     
    136141  files.set('manifest.json', Buffer.from(JSON.stringify(m)));
    137142  const r = AI.importArchive(files, { slug: 'me' });
    138   assert.equal(r.idsBehouden, false);
     143  assert.equal(r.idsBehouden, false, 'de AP-URI verandert wel, en dat blijft gemeld');
    139144  assert.match(r.waarschuwingen.join(' '), /origin verschilt/);
    140   assert.equal(db.prepare("SELECT COUNT(*) AS n FROM posts WHERE id = 'zwaar'").get().n, 0);
    141   assert.equal(db.prepare('SELECT COUNT(*) AS n FROM posts').get().n, 1, 'wel geimporteerd, met een nieuw id');
     145  assert.equal(db.prepare("SELECT COUNT(*) AS n FROM posts WHERE id = 'zwaar'").get().n, 1,
     146    'het interne id komt ongewijzigd terug');
     147  assert.equal(db.prepare('SELECT COUNT(*) AS n FROM posts').get().n, 1);
    142148});
    143149
  • test/fep1580-migration.test.js

    r919b82d r19430fa  
    481481});
    482482
    483 test('de [[track:]]-verwijzing in een bericht wordt bijgetrokken', async () => {
     483test('de [[track:]]-verwijzing in een bericht blijft naar een bestaand nummer wijzen', async () => {
    484484  // Wat Robin op TikTik zag: het bericht toonde de shorthand zelf in plaats van
    485485  // een speler. Zijn posts kwamen uit de ZIP (die bewaart posts.content
     
    515515  }));
    516516
     517  // DE EIS, ongeacht hoe: na een verhuizing wijst de shorthand naar een nummer
     518  // dat hier bestaat. Sinds "altijd behouden" (14-8) klopt dat meestal vanzelf,
     519  // want het id verandert niet meer. Botst het id wel, dan trekt de ingest de
     520  // tekst bij. Deze test toetst de UITKOMST en niet de route ernaartoe.
    517521  const inhoud = db.prepare("SELECT content FROM posts WHERE id = 'pz'").get().content;
    518522  const m = /\[\[track:([^\]]+)\]\]/.exec(inhoud);
    519   assert.ok(m, 'de shorthand blijft staan, alleen het id verandert');
    520   assert.notEqual(m[1], 't-oud', 'het oude id wijst hier nergens heen');
     523  assert.ok(m, 'de shorthand blijft staan');
    521524  const bestaat = db.prepare('SELECT 1 FROM audio_tracks WHERE id = ? AND site_id = ?').get(m[1], 's1');
    522   assert.ok(bestaat, 'en het nieuwe id hoort bij een nummer dat er echt is');
     525  assert.ok(bestaat, `[[track:${m[1]}]] hoort bij een nummer dat er echt is`);
     526  assert.equal(m[1], 't-oud', 'en omdat het id behouden blijft, hoefde er niets herschreven');
     527});
     528
     529test('botst het track-id wel, dan wordt de tekst bijgetrokken', async () => {
     530  // Het vangnet. "Altijd behouden" kan niet als er hier al iets anders met dat
     531  // id staat; dan krijgt het nummer een ander id en moet de shorthand mee.
     532  const s2 = site({ aliases: [BRON] });
     533  db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, content, status, published_at)
     534              VALUES ('pb','s1','u1','botsing','Botsing','<p>[[track:t-bots]]</p>','published','2026-08-13T10:00:00Z')`).run();
     535  // Een nummer dat hier AL bestaat onder datzelfde id, van iets anders.
     536  db.prepare("INSERT INTO media (id, site_id, filename, mime_type, size, storage_path) VALUES ('mx','s1','x.mp3','audio/mpeg',1,'/x')").run();
     537  db.prepare("INSERT INTO audio_tracks (id, site_id, title, media_id) VALUES ('t-bots','s1','Al van mij','mx')").run();
     538
     539  const kaart2 = new Map([
     540    [BRON, { id: BRON, type: 'Person', movedTo: IK, outbox: `${BRON}/outbox`, streams: [`${BRON}/tracks`] }],
     541    [`${BRON}/outbox`, { type: 'OrderedCollection', orderedItems: [] }],
     542    [`${BRON}/tracks`, {
     543      type: 'OrderedCollection',
     544      orderedItems: [{
     545        id: `${BRON}/tracks/t-bots`, type: 'Audio', name: 'Van de bron',
     546        url: [{ type: 'Link', href: `${BRON}/audio/y.mp3`, mediaType: 'audio/mpeg' }],
     547      }],
     548    }],
     549  ]);
     550  await stil(() => Mig.ingestFromSource(s2, {
     551    deps: {
     552      getJson: async (_slug, url) => kaart2.get(url) || null,
     553      noteId: (b, id) => `${b}/ap/notes/${id}`,
     554      noteVisibility: AP.noteVisibility,
     555      audioRoot: '/nep/audio', mediaRoot: '/nep/media',
     556      signHeaders: () => ({ Signature: 'nep' }),
     557      safeFetch: async () => ({ ok: true, arrayBuffer: async () => Buffer.from('y'), headers: { get: () => 'audio/mpeg' } }),
     558      fs: { mkdirSync() {}, writeFileSync() {} },
     559      path,
     560    },
     561  }));
     562  // Het bestaande nummer blijft van jou; de tekst wijst naar iets dat bestaat.
     563  const inhoud = db.prepare("SELECT content FROM posts WHERE id = 'pb'").get().content;
     564  const m = /\[\[track:([^\]]+)\]\]/.exec(inhoud);
     565  assert.ok(m);
     566  assert.ok(db.prepare('SELECT 1 FROM audio_tracks WHERE id = ? AND site_id = ?').get(m[1], 's1'),
     567    'wat er ook gebeurde met het id, de verwijzing wijst naar een bestaand nummer');
    523568});
    524569
Note: See TracChangeset for help on using the changeset viewer.