Ignore:
Timestamp:
08/06/2026 09:33:58 AM (5 weeks ago)
Author:
Claude (agent) <aiclaude@…>
Branches:
main
Children:
25f2e64
Parents:
6bc5ebd
git-author:
Robin <roboburr@…> (08/06/2026 09:33:57 AM)
git-committer:
Claude (agent) <aiclaude@…> (08/06/2026 09:33:58 AM)
Message:

Reactie-migratie draait bij boot, inclusief hersleutelen (shaer-9e9)

Een upgrade naar de fase-2-code was tot nu toe onveilig. Het schema migreert
vanzelf (ensureColumn bij boot), maar voor data was er niets: de backfill was een
handmatig script. Bij een vloot die met klonkt-update in een stap naar nieuwe code
gaat wordt dat vergeten, en het falen is stil -- een reactie die niemand meer
ziet geeft geen fout, en de gebruiker klikt gewoon opnieuw, met een tweede Like
de fediverse in als gevolg.

migrateReactions() draait nu bij boot, één keer per REACTIONS_MIGRATION_VERSION,
op dezelfde manier als selfHealTimeline. Twee stappen:

HERSLEUTELEN De oude interact-route bewaarde de URI waarmee je binnenkwam, en

de bookmarklet geeft window.location.href door -- dus de
permalink. Sinds canonicalReactionUri op de object-URI gezocht
wordt, zouden die rijen wees zijn. Dit gat zat NIET in mijn
eerste backfill-script; dat vulde alleen aan vanuit de kolommen
en zag deze rijen niet. Op dev viel het niet op omdat daar alles
al canoniek stond.

AANVULLEN Alles wat op oude code via de Krant is gegeven staat alleen in

ap_timeline.liked/boosted.

Volgorde binnen het hersleutelen: eerst de canonieke variant erbij, dan de
permalink weg. Een onderbreking levert dan hooguit een dubbele rij op en nooit
een verdwenen reactie. Bij hersleutelen reist created_at mee; bij aanvullen
bestaat die informatie niet en wordt het de migratiedatum.

Mislukt de migratie, dan wordt de versievlag niet gezet en probeert de volgende
boot het opnieuw. De kolommen blijven staan, dus de oude waarheid is intussen
niet weg.

Het losse script is nu een schil om diezelfde functie, met --dry-run en --force.
Twee implementaties van een migratie lopen uiteen, en dan repareert de ene wat de
andere niet ziet.

Droogloop tegen de dev-database: 0 te hersleutelen, 0 aan te vullen -- die is al
schoon, wat meteen de idempotentie bevestigt. Suite 438/438.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • test/reactions-characterization.test.js

    r6bc5ebd r7842ca1  
    313313  assert.equal(AP.getReaction('me', onbekend).liked, true);
    314314});
     315
     316// ── De migratie bij boot ─────────────────────────────────────────────────
     317
     318test('migrateReactions hersleutelt een permalink-rij naar de object-URI', () => {
     319  // De oude interact-route bewaarde de URI waarmee je binnenkwam, en de
     320  // bookmarklet geeft de permalink door. Zonder hersleutelen zijn die rijen
     321  // wees zodra er op de object-URI gezocht wordt.
     322  const obj = uri('mig1');
     323  const permalink = 'https://sound-fabrics.com/oud-artikel';
     324  db.prepare(`INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, content, url, created_at)
     325              VALUES (?,?,?,?,?,?,?)`)
     326    .run(obj, 'me', 'https://r.test/users/anna', 'Anna', '<p>x</p>', permalink, '2026-08-06 09:00:00');
     327  db.prepare("INSERT OR IGNORE INTO ap_my_reactions (site_slug, target_uri, kind, created_at) VALUES (?,?,?,?)")
     328    .run('me', permalink, 'like', '2026-07-01 12:00:00');
     329
     330  const uitkomst = AP.migrateReactions({ force: true });
     331  assert.ok(uitkomst.hersleuteld >= 1);
     332  assert.equal(AP.getReaction('me', obj).liked, true, 'nu vindbaar op de object-uri');
     333  const rij = db.prepare('SELECT created_at FROM ap_my_reactions WHERE site_slug=? AND target_uri=? AND kind=?').get('me', obj, 'like');
     334  assert.equal(rij.created_at, '2026-07-01 12:00:00', 'bij hersleutelen blijft de oorspronkelijke datum staan');
     335  const oud = db.prepare('SELECT COUNT(*) AS n FROM ap_my_reactions WHERE target_uri=?').get(permalink).n;
     336  assert.equal(oud, 0, 'de permalink-rij is opgeruimd');
     337});
     338
     339test('migrateReactions vult een kale kolomvlag aan', () => {
     340  const u = uri('mig2'); seedTimeline(u);
     341  AP.markLiked('me', u);                                  // zoals de oude Krant-route
     342  assert.equal(AP.getReaction('me', u).liked, false, 'vooraf onzichtbaar');
     343  AP.migrateReactions({ force: true });
     344  assert.equal(AP.getReaction('me', u).liked, true, 'daarna zichtbaar');
     345});
     346
     347test('migrateReactions is idempotent en respecteert de versievlag', () => {
     348  const eerste = AP.migrateReactions({ force: true });
     349  const tweede = AP.migrateReactions({ force: true });
     350  assert.equal(tweede.hersleuteld, 0, 'niets meer te hersleutelen');
     351  assert.equal(tweede.aangevuld, 0, 'niets meer aan te vullen');
     352  assert.ok(eerste.hersleuteld >= 0);
     353  // Zonder force draait hij niet nog eens zodra de vlag staat.
     354  db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?,?)').run('reactions_migration_version', '1');
     355  assert.equal(AP.migrateReactions().overgeslagen, true);
     356});
     357
     358test('migrateReactions --dry-run schrijft niets', () => {
     359  const u = uri('mig3'); seedTimeline(u);
     360  AP.markLiked('me', u);
     361  const telling = AP.migrateReactions({ force: true, dryRun: true });
     362  assert.ok(telling.aangevuld >= 1, 'hij ziet wel wat er te doen is');
     363  assert.equal(AP.getReaction('me', u).liked, false, 'maar heeft niets geschreven');
     364});
Note: See TracChangeset for help on using the changeset viewer.