Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/config/database.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -1002,4 +1002,34 @@
     db.prepare("UPDATE posts SET cover_image_url = NULL WHERE cover_image_url LIKE '/media/reply-media/%' AND instr(content, cover_image_url) > 0").run();
   } catch { /* posts table absent on fresh init */ }
+  // Bestaande rijen naar EEN spelling (shaer-a937). De schrijfwegen leveren
+  // sinds deze release ISO; dit haalt na wat er in SQL-notatie is blijven
+  // staan, zodat de sortering ook zonder de isoSql-wikkel klopt.
+  //
+  // Alleen rijen met een spatie op positie 11 en geen 'T': dat is precies de
+  // CURRENT_TIMESTAMP-vorm. Idempotent -- een tweede keer draaien vindt niets
+  // meer -- en het raakt een ISO-stempel nooit aan.
+  //
+  // strftime geeft NULL op iets dat het niet als tijd herkent; de WHERE laat
+  // zulke rijen met rust, want een onleesbare stempel vervangen door NULL is
+  // gegevens weggooien. Ze blijven staan zoals ze stonden.
+  {
+    const kolommen = [
+      ['ap_timeline', 'created_at'], ['ap_timeline', 'published'],
+      ['ap_mentions', 'created_at'], ['ap_mentions', 'published'],
+      ['ap_interactions', 'created_at'], ['ap_interactions', 'published'],
+      ['ap_outbox', 'created_at'],
+    ];
+    let veranderd = 0;
+    for (const [tabel, kolom] of kolommen) {
+      try {
+        const r = db.prepare(
+          `UPDATE ${tabel} SET ${kolom} = strftime('%Y-%m-%dT%H:%M:%SZ', ${kolom})
+            WHERE ${kolom} IS NOT NULL AND ${kolom} LIKE '____-__-__ %'
+              AND strftime('%Y-%m-%dT%H:%M:%SZ', ${kolom}) IS NOT NULL`).run();
+        veranderd += r.changes;
+      } catch { /* tabel bestaat nog niet op een verse installatie */ }
+    }
+    if (veranderd) console.log(`🕒 tijdstempels genormaliseerd: ${veranderd} rijen`);
+  }
   ensureColumn('ap_mentions', 'wave', 'INTEGER');  // inbound guardian wave
   // FEP-633c §2.2: object hint that the author is a ward. Register-only for now;
Index: src/routes/auth.js
===================================================================
--- src/routes/auth.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/routes/auth.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -223,5 +223,5 @@
   const row = db.prepare(`
     SELECT id, username FROM users
-    WHERE reset_token = ? AND reset_token_expires > datetime('now')
+    WHERE reset_token = ? AND datetime(reset_token_expires) > datetime('now')
   `).get(hashToken(req.params.token));
   renderPage(req, res, 'pages/auth-reset', {
@@ -237,5 +237,5 @@
   const row = db.prepare(`
     SELECT id, username FROM users
-    WHERE reset_token = ? AND reset_token_expires > datetime('now')
+    WHERE reset_token = ? AND datetime(reset_token_expires) > datetime('now')
   `).get(hashToken(req.params.token));
 
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/services/ActivityPubService.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -19,5 +19,5 @@
 import fs from 'fs';
 import path from 'path';
-import db from '../config/database.js';
+import db, { NU_ISO } from '../config/database.js';
 import HtmlSanitizerService from './HtmlSanitizerService.js';
 import AudioEmbedService from './AudioEmbedService.js';
@@ -1482,10 +1482,10 @@
 function iStmts() {
   if (!_insI) {
-    _insI = db.prepare('INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, parent_uri, visibility, emoji_json, actor_emoji_json, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
+    _insI = db.prepare(`INSERT OR IGNORE INTO ap_interactions (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, parent_uri, visibility, emoji_json, actor_emoji_json, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`);
     _delLA = db.prepare('DELETE FROM ap_interactions WHERE kind = ? AND post_id = ? AND actor_uri = ?');
     _delReply = db.prepare("DELETE FROM ap_interactions WHERE kind = 'reply' AND object_uri = ?");
     _listI = db.prepare('SELECT id, kind, object_uri, parent_uri, actor_uri, actor_name, actor_handle, actor_url, actor_icon, content, published, created_at, acted_boost, acted_like, visibility, emoji_json, actor_emoji_json FROM ap_interactions WHERE post_id = ? ORDER BY created_at ASC');
     _getI = db.prepare('SELECT * FROM ap_interactions WHERE id = ?');
-    _insO = db.prepare('INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
+    _insO = db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,${NU_ISO})`);
     _listO = db.prepare('SELECT * FROM ap_outbox WHERE post_id = ? ORDER BY created_at ASC');
     _getO = db.prepare('SELECT * FROM ap_outbox WHERE id = ?');
Index: src/services/ArchiveImportService.js
===================================================================
--- src/services/ArchiveImportService.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/services/ArchiveImportService.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -23,5 +23,5 @@
 import crypto from 'crypto';
 import { randomUUID } from 'crypto';
-import db from '../config/database.js';
+import db, { NU_ISO } from '../config/database.js';
 import { MEDIA_ROOT, AUDIO_ROOT } from '../config/paths.js';
 import { FORMAT_VERSION, parseFollowingCsv } from './ArchiveExportService.js';
@@ -413,5 +413,5 @@
   const insReply = db.prepare(`INSERT OR IGNORE INTO ap_interactions
     (kind, post_id, object_uri, actor_uri, actor_name, actor_handle, content, published, parent_uri, created_at)
-    VALUES ('reply', ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`);
+    VALUES ('reply', ?, ?, ?, ?, ?, ?, ?, ?, ${NU_ISO})`);
 
   db.transaction(() => {
Index: src/services/SqliteSessionStore.js
===================================================================
--- src/services/SqliteSessionStore.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/services/SqliteSessionStore.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -35,8 +35,8 @@
     this._stmtDestroy = db.prepare('DELETE FROM sessions WHERE sid = ?');
     this._stmtTouch = db.prepare('UPDATE sessions SET expiresAt = ? WHERE sid = ?');
-    this._stmtCount = db.prepare("SELECT COUNT(*) AS c FROM sessions WHERE expiresAt > datetime('now')");
+    this._stmtCount = db.prepare("SELECT COUNT(*) AS c FROM sessions WHERE datetime(expiresAt) > datetime('now')");
     this._stmtClear = db.prepare('DELETE FROM sessions');
-    this._stmtAll = db.prepare("SELECT sid, data FROM sessions WHERE expiresAt > datetime('now')");
-    this._stmtGc = db.prepare("DELETE FROM sessions WHERE expiresAt <= datetime('now')");
+    this._stmtAll = db.prepare("SELECT sid, data FROM sessions WHERE datetime(expiresAt) > datetime('now')");
+    this._stmtGc = db.prepare("DELETE FROM sessions WHERE datetime(expiresAt) <= datetime('now')");
   }
 
Index: src/services/ap-inbox.js
===================================================================
--- src/services/ap-inbox.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/services/ap-inbox.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -17,5 +17,5 @@
  * guardian-broers in de dienst, zoals gateOutgoingFollow bij stap 7.
  */
-import db from '../config/database.js';
+import db, { NU_ISO } from '../config/database.js';
 import HtmlSanitizerService from './HtmlSanitizerService.js';
 import * as Guardianship from './guardianship/index.js';
@@ -130,5 +130,5 @@
     if (++_seenSinceSnoei >= 500) {
       _seenSinceSnoei = 0;
-      const r = db.prepare(`DELETE FROM ap_seen_notes WHERE created_at < datetime('now', '-${SEEN_NOTES_DAYS} days')`).run();
+      const r = db.prepare(`DELETE FROM ap_seen_notes WHERE datetime(created_at) < datetime('now', '-${SEEN_NOTES_DAYS} days')`).run();
       if (r.changes) console.log(`[AP] seen notes: ${r.changes} pruned`);
     }
@@ -751,5 +751,5 @@
             const ouder = safeUrl(typeof o.inReplyTo === 'string' ? o.inReplyTo : (o.inReplyTo && o.inReplyTo.id)) || null;
             const r = db.prepare(`INSERT OR IGNORE INTO ap_mentions (slug, object_uri, note_url, actor_uri, actor_name, actor_handle, actor_icon, actor_url, content, published, in_reply_to, help_request, wave, has_guardians, emoji_json, actor_emoji_json, media_json, created_at)
-                                  VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
+                                  VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`)
               .run(slug, o.id, safeUrl(o.url) || null, actorUri, ai.name, ai.handle, ai.icon, ai.url, html, o.published || null, ouder, help ? 1 : 0, wave ? 1 : 0, hasG ? 1 : 0,
                 extractEmojiTags(o.tag), emojiJsonOf(ai.emojis), mediaFromNote(o));
Index: src/services/ap-timeline.js
===================================================================
--- src/services/ap-timeline.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/services/ap-timeline.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -13,5 +13,5 @@
  * wireTimeline, hetzelfde injectiepatroon als guardianship en ap-c2s.
  */
-import db, { isoSql } from '../config/database.js';
+import db, { isoSql, NU_ISO } from '../config/database.js';
 
 // De helper woont sinds shaer-a937 in config/database.js: elke plek die
@@ -30,5 +30,5 @@
 export function tlStmts() {
   if (!_insTl) {
-    _insTl = db.prepare('INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon, author_url, content, url, published, media_json, nsfw, cw, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)');
+    _insTl = db.prepare(`INSERT OR IGNORE INTO ap_timeline (id, slug, author_uri, author_name, author_handle, author_icon, author_url, content, url, published, media_json, nsfw, cw, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`);
     _listTl = db.prepare(`SELECT * FROM ap_timeline WHERE slug = ? ORDER BY ${STEMPEL('COALESCE(published, created_at)')} DESC LIMIT ? OFFSET ?`);
     _delTl = db.prepare('DELETE FROM ap_timeline WHERE id = ?');
Index: src/services/guardianship/delivery.js
===================================================================
--- src/services/guardianship/delivery.js	(revision 8151340aee19dd0280b5185a8c3e9c56dc279653)
+++ src/services/guardianship/delivery.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -12,5 +12,5 @@
  */
 import crypto from 'crypto';
-import db from '../../config/database.js';
+import db, { NU_ISO } from '../../config/database.js';
 import { carriesGuardians } from './context.js';
 
@@ -124,5 +124,5 @@
   const id = crypto.randomUUID();
   db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, wave, away_until, created_at)
-              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
+              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`)
     .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0, wave ? 1 : 0, awayUntil || null);
   const row = getOutboxRow(id);
Index: test/timestamp-source.test.js
===================================================================
--- test/timestamp-source.test.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
+++ test/timestamp-source.test.js	(revision 9946a68c3cdf4796d3e5ab4c7f6e3317e0f40984)
@@ -0,0 +1,87 @@
+// De BRON schrijft nog maar een spelling, en wat er al stond is omgezet
+// (shaer-a937, de duurzame helft).
+//
+// De leeskant wikkelt inmiddels alles in isoSql, dus een gemengde kolom sorteert
+// toch goed. Dat is het vangnet, niet de oplossing: zolang er twee vormen
+// binnenkomen blijft elke NIEUWE query die iemand schrijft een kans om het
+// opnieuw fout te doen. Deze toets bewaakt dat er niets nieuws bijkomt.
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://test.example';
+
+const dbMod = await import('../src/config/database.js');
+const db = dbMod.default;
+{ const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
+
+const ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
+
+test('een rij die de APP wegschrijft draagt ISO', async () => {
+  // Door de echte schrijfweg (tlStmts), niet met een eigen INSERT: een toets
+  // die zijn eigen ISO invoegt bewijst alleen dat de kolom tekst opslaat, en
+  // kan per definitie niet falen op wat hier veranderd is.
+  const AP = (await import('../src/services/ActivityPubService.js')).default;
+  AP.upsertBoostedNote('kid', {
+    object_uri: 'https://a.example/n/vers', actor_uri: 'https://a.example/u',
+    actor_name: 'A', content: '<p>vers</p>', media: '[]',
+  });
+  const r = db.prepare("SELECT created_at FROM ap_timeline WHERE id = 'https://a.example/n/vers'").get();
+  assert.ok(r, 'de rij hoort er te zijn');
+  assert.match(r.created_at, ISO, 'een verse rij hoort ISO te dragen, niet de SQL-notatie');
+});
+
+test('geen enkele schrijfweg naar die tabellen schrijft nog CURRENT_TIMESTAMP', async () => {
+  // Op de BRON getoetst en niet op gedrag: de fout is dat iemand later een
+  // INSERT toevoegt met de oude vorm, en dat merk je pas als de volgorde in de
+  // app scheef staat. De tabellen hieronder zijn degene waar de menging in zat.
+  const fs = await import('node:fs');
+  const path = await import('node:path');
+  const { fileURLToPath } = await import('node:url');
+  const wortel = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'src');
+
+  const bestanden = [];
+  (function loop(map) {
+    for (const naam of fs.readdirSync(map, { withFileTypes: true })) {
+      const p = path.join(map, naam.name);
+      if (naam.isDirectory()) loop(p);
+      else if (naam.name.endsWith('.js')) bestanden.push(p);
+    }
+  })(wortel);
+
+  const TABELLEN = /INSERT(?:\s+OR\s+\w+)?\s+INTO\s+(ap_timeline|ap_mentions|ap_interactions|ap_outbox)\b/i;
+  const fout = [];
+  for (const p of bestanden) {
+    if (p.endsWith(path.join('config', 'database.js'))) continue;   // daar staat het schema zelf
+    const bron = fs.readFileSync(p, 'utf8');
+    // Per statement kijken: een bestand mag elders best CURRENT_TIMESTAMP
+    // gebruiken voor een tabel die hier niet over gaat.
+    for (const stuk of bron.split(/db\.prepare\(/)) {
+      if (TABELLEN.test(stuk) && /CURRENT_TIMESTAMP/.test(stuk.split(')')[0] + stuk.slice(0, 600))) {
+        fout.push(path.relative(wortel, p));
+      }
+    }
+  }
+  assert.deepEqual([...new Set(fout)], [],
+    'schrijf hier strftime(\'%Y-%m-%dT%H:%M:%SZ\',\'now\'), anders komt de menging terug');
+});
+
+test('de migratie zet oude SQL-rijen om en laat de rest met rust', () => {
+  db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content, created_at)
+              VALUES ('reply','p1','https://a.example/n/oud','https://a.example/u','<p>oud</p>','2026-08-06 09:02:01')`).run();
+  db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content, created_at)
+              VALUES ('reply','p1','https://a.example/n/iso','https://a.example/u','<p>iso</p>','2026-08-06T02:08:01.000Z')`).run();
+  db.prepare(`INSERT INTO ap_interactions (kind, post_id, object_uri, actor_uri, content, created_at)
+              VALUES ('reply','p1','https://a.example/n/raar','https://a.example/u','<p>raar</p>','geen datum')`).run();
+
+  { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
+
+  const lees = (uri) => db.prepare('SELECT created_at FROM ap_interactions WHERE object_uri = ?').get(uri).created_at;
+  assert.equal(lees('https://a.example/n/oud'), '2026-08-06T09:02:01Z', 'de SQL-vorm is omgezet');
+  assert.equal(lees('https://a.example/n/iso'), '2026-08-06T02:08:01.000Z', 'een ISO-stempel blijft ongemoeid');
+  assert.equal(lees('https://a.example/n/raar'), 'geen datum', 'een onleesbare stempel wordt niet weggegooid');
+
+  // Idempotent: nog een keer draaien verandert niets meer.
+  { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
+  assert.equal(lees('https://a.example/n/oud'), '2026-08-06T09:02:01Z', 'tweede keer draaien laat het staan');
+});
