Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 29eb7459b738dadb3363af52947ca6358844a4e9)
+++ src/services/ActivityPubService.js	(revision dc0da728058abbb34eae659abf791fd8849c2c9c)
@@ -3381,18 +3381,22 @@
     if (g) return g;
   }
+  // Een gate-voorstel uit de app (5.6, shaer-8ru): een Offer van een
+  // shaer:GatedSetting, de vorm die 5.6 al beschrijft.
+  //
+  // HIER, EN GEEN `case` IN DE SWITCH. Dat was hij eerst, en die claimde ELKE
+  // Offer: wat geen gate-voorstel was kreeg 400 unsupported_offer -- ook de
+  // adoptie-handshake, en straks elke Offer-vorm die we nog toevoegen. Barts
+  // honderd aanbiedingen liepen er meteen op stuk. Alleen claimen wat je
+  // herkent, en de rest laten doorlopen.
+  if (type === 'Offer') {
+    const gs = Guardianship.gated.parseGatedSetting(activity.object);
+    if (gs) {
+      const uit = proposeGate(site, gs.ward, gs.feature, gs.value);
+      return uit.status === 200 ? { ...uit, status: 201, id: uit.offerId } : uit;
+    }
+  }
 
   try {
     switch (type) {
-      // Een gate-voorstel uit de app (5.6, shaer-8ru). De PWA deed dit al over
-      // een eigen route; hier komt het binnen als wat het in AP IS -- een Offer
-      // van een shaer:GatedSetting. Dezelfde functie erachter, want twee wegen
-      // naar hetzelfde besluit is precies wat we vandaag hebben rechtgezet.
-      case 'Offer': {
-        const gs = Guardianship.gated.parseGatedSetting(object);
-        if (!gs) return { status: 400, error: 'unsupported_offer' };
-        const uit = proposeGate(site, gs.ward, gs.feature, gs.value);
-        if (uit.status !== 200) return uit;
-        return { ...uit, status: 201, id: uit.offerId };
-      }
       case 'Create': {
         if (!object || typeof object !== 'object') return { status: 400, error: 'missing_object' };
Index: test/gated-propose-c2s.test.js
===================================================================
--- test/gated-propose-c2s.test.js	(revision 29eb7459b738dadb3363af52947ca6358844a4e9)
+++ test/gated-propose-c2s.test.js	(revision dc0da728058abbb34eae659abf791fd8849c2c9c)
@@ -61,8 +61,20 @@
 });
 
-test('een Offer dat GEEN gate-voorstel is wordt niet stilletjes geslikt', async () => {
+test('een Offer dat GEEN gate-voorstel is wordt NIET geclaimd', async () => {
+  // WAS: dit gaf 400 unsupported_offer, en dat was fout. Deze tak claimde elke
+  // Offer, dus ook de adoptie-handshake -- Barts honderd aanbiedingen liepen er
+  // meteen op stuk. Alleen claimen wat je herkent.
   const uit = await AP.ingestOutboxActivity(site(), user, { type: 'Offer', object: { type: 'Note', content: 'hoi' } });
-  assert.equal(uit.status, 400);
-  assert.equal(uit.error, 'unsupported_offer');
+  assert.notEqual(uit && uit.error, 'unsupported_offer');
+});
+
+test('en een adoptie-Offer komt bij de handshake terecht', async () => {
+  // De regressie die Bart ving, als toets. Een Relationship-offer hoort NOOIT
+  // door de gate-tak afgevangen te worden.
+  const uit = await AP.ingestOutboxActivity(site(), user, {
+    type: 'Offer', to: [KIND],
+    object: { type: 'Relationship', subject: KIND, object: 'https://oma.test/ap/users/oma', relationship: 'shaer:Guardian' },
+  });
+  assert.notEqual(uit && uit.error, 'unsupported_offer');
 });
 
Index: test/ssrf-allowlist.test.js
===================================================================
--- test/ssrf-allowlist.test.js	(revision 29eb7459b738dadb3363af52947ca6358844a4e9)
+++ test/ssrf-allowlist.test.js	(revision dc0da728058abbb34eae659abf791fd8849c2c9c)
@@ -12,5 +12,8 @@
 
 process.env.DATABASE_PATH = ':memory:';
-process.env.AP_ALLOW_HOSTS = '[::1]:3060';
+// Een poort waar met opzet NIETS op luistert. Eerst stond hier 3060, en toen de
+// testkudde daar ging draaien slaagde de fetch en viel de toets om -- een toets
+// die afhangt van wat er toevallig draait, bewijst niets.
+process.env.AP_ALLOW_HOSTS = '[::1]:59321';
 const AP = await import('../src/services/ActivityPubService.js');
 
@@ -25,6 +28,6 @@
   // niets op 3060, dus we verwachten een VERBINDINGSfout -- het bewijs is dat
   // het geen ssrf-fout is.
-  const fout = await faalt('http://[::1]:3060/u/w001');
-  assert.ok(fout, 'er is wel degelijk een fout, want er luistert niets');
+  const fout = await faalt('http://[::1]:59321/niets');
+  assert.ok(fout, 'er is wel een fout, want er luistert niets op die poort');
   assert.ok(!String(fout).startsWith('ssrf-'), `door de poort, maar kreeg: ${fout}`);
 });
@@ -32,10 +35,10 @@
 test('een adres dat NIET op de lijst staat blijft geweigerd', async () => {
   // Zelfde machine, andere poort. Zou dit doorlaten, dan is de lijst een vlag.
-  assert.equal(await faalt('http://[::1]:3061/u/w001'), 'ssrf-blocked-ip');
+  assert.equal(await faalt('http://[::1]:3060/u/w001'), 'ssrf-blocked-ip');
 });
 
 test('en 127.0.0.1 evenmin, ook al is het dezelfde machine', async () => {
   // De lijst opent een HOST:POORT, geen begrip van "lokaal".
-  assert.equal(await faalt('http://127.0.0.1:3060/u/w001'), 'ssrf-blocked-ip');
+  assert.equal(await faalt('http://127.0.0.1:59321/niets'), 'ssrf-blocked-ip');
 });
 
@@ -52,5 +55,5 @@
   const mod = await import(`../src/services/ActivityPubService.js?leeg=${Date.now()}`);
   let fout = null;
-  try { await mod.safeFetch('http://[::1]:3060/u/w001'); } catch (e) { fout = e.message; }
+  try { await mod.safeFetch('http://[::1]:59321/niets'); } catch (e) { fout = e.message; }
   assert.equal(fout, 'ssrf-blocked-ip');
   process.env.AP_ALLOW_HOSTS = eerder;
Index: tools/ward-farm/farm.mjs
===================================================================
--- tools/ward-farm/farm.mjs	(revision 29eb7459b738dadb3363af52947ca6358844a4e9)
+++ tools/ward-farm/farm.mjs	(revision dc0da728058abbb34eae659abf791fd8849c2c9c)
@@ -286,4 +286,4 @@
 
 server.listen(PORT, '::1', () => {
-  console.log(`[farm] ${N} wards op ${BASE} (luistert op 127.0.0.1:${PORT})`);
+  console.log(`[farm] ${N} wards op ${BASE} (luistert op [::1]:${PORT})`);
 });
Index: tools/ward-farm/offer-many.mjs
===================================================================
--- tools/ward-farm/offer-many.mjs	(revision 29eb7459b738dadb3363af52947ca6358844a4e9)
+++ tools/ward-farm/offer-many.mjs	(revision dc0da728058abbb34eae659abf791fd8849c2c9c)
@@ -61,5 +61,7 @@
       subject: ward,
       object: me,
-      relationship: 'https://shaer.klonkt.com/ns#guardian',
+      // De exacte waarde die isGuardianRelationship accepteert. Mijn eerste
+      // poging gebruikte een verzonnen URI en werd stil geweigerd.
+      relationship: 'shaer:Guardian',
     },
   };
