Index: test/queue-keys-contract.test.js
===================================================================
--- test/queue-keys-contract.test.js	(revision 2165b6d3d57ceaa8b5d1f70491868482149470cf)
+++ test/queue-keys-contract.test.js	(revision 2165b6d3d57ceaa8b5d1f70491868482149470cf)
@@ -0,0 +1,65 @@
+// Wat de actor als wachtrij ADVERTEERT, moet er ook zijn -- en de lijst mag
+// niet stilletjes groeien (shaer-x3kj, contract uit shaer-6d9).
+//
+// De daemon en Klonkt delen dit contract: een client die tegen allebei praat
+// mag geen sleutel op de een vinden en op de ander niet. Zo ontstond deze bead:
+// Klonkt kreeg er outgoingFollows en help bij, de daemon bleef op vier staan,
+// en de UI die je tegen de daemon test zegt dan niets over Klonkt.
+//
+// Deze toets bewaakt de kant die HIER te bewaken is:
+//   1. de sleutelverzameling ligt vast, dus een zevende sleutel is een bewuste
+//      daad met een rode toets ernaast in plaats van stille drift;
+//   2. elke geadverteerde sleutel wijst naar een route die BESTAAT. Adverteren
+//      wat er niet is, is erger dan niet adverteren -- dan gaat een client hem
+//      halen en krijgt 404 op iets wat het actor-document beloofde.
+//
+// De spiegelkant (draait de daemon dezelfde zes?) hoort in de daemon zelf; die
+// heeft geen toegang tot deze suite en andersom.
+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 express = (await import('express')).default;
+const routes = (await import('../src/routes/activitypub.js')).default;
+
+db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@t', 'x', 'god');
+db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public, is_primary) VALUES (?,?,?,?,1,1)').run('s1', 'kid', 'Kid', 'u1');
+
+const app = express(); app.use(routes);
+const server = app.listen(0);
+await new Promise((r) => server.once('listening', r));
+const base = `http://127.0.0.1:${server.address().port}`;
+test.after(() => server.close());
+
+// De zes die Klonkt vandaag adverteert. Komt er een bij, dan hoort hij hier
+// EN in de daemon -- dat is het hele punt van shaer-6d9.
+const VERWACHT = ['follows', 'guardians', 'help', 'offers', 'outgoingFollows', 'wards'];
+
+async function queues() {
+  const r = await fetch(`${base}/ap/users/kid`, { headers: { Accept: 'application/activity+json' } });
+  assert.equal(r.status, 200, 'het actor-document hoort er te zijn');
+  return (await r.json())['shaer:queues'] || {};
+}
+
+test('de actor adverteert precies de afgesproken wachtrijen', async () => {
+  assert.deepEqual(Object.keys(await queues()).sort(), [...VERWACHT].sort(),
+    'wijkt dit af, dan loopt Klonkt uit de pas met de daemon (shaer-x3kj)');
+});
+
+test('elke geadverteerde wachtrij bestaat als route', async () => {
+  const q = await queues();
+  const missend = [];
+  for (const [sleutel, url] of Object.entries(q)) {
+    // Zonder token geeft een bestaande wachtrij 403 (alleen de eigenaar mag
+    // erin). 404 betekent dat de route niet bestaat -- dan belooft het
+    // actor-document iets wat er niet is.
+    const r = await fetch(base + new URL(url).pathname, { headers: { Accept: 'application/activity+json' } });
+    if (r.status === 404) missend.push(`${sleutel} -> ${new URL(url).pathname}`);
+  }
+  assert.deepEqual(missend, [], 'geadverteerd maar niet bereikbaar');
+});
