Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision 21ef238b2aef0be15654f3a397db52757fa168b1)
+++ src/routes/activitypub.js	(revision e2150909dd29bd970c606ecdc6b09f233415d3a8)
@@ -377,4 +377,39 @@
     },
   };
+}
+
+/** Wat deze lezer mag (FEP-633c 5.6), op EEN plek.
+ *
+ *  De verschil-lezing draagt ze net zo goed: een antwoord zonder rechten zou
+ *  de client naar zijn standaard laten terugvallen, en die standaard is
+ *  'alles mag'. Dan zet een gesloten poort zichzelf stil open. Dezelfde reden
+ *  waarom een 304 de caps met rust laat.
+ */
+function capabilitiesOf(p, gate) {
+  return {
+      'shaer:externalEmbeds': p.embedsAllowed,
+      'shaer:externalPlayback': p.playbackAllowed,
+      // Leaving the app is the same decision as playing inside it: with the
+      // gate shut a link is shown but not followed, so the door is closed too
+      // and not just the picture over it.
+      'shaer:externalLinks': p.playbackAllowed,
+      // De rest van de familie (8-8): de app hoort VOORAF te weten wat hij mag
+      // aanbieden in plaats van het bij de eerste weigering te ontdekken. De
+      // (+) kaart leest shaer:compose al (Barts gate); de rest is er voor de
+      // schermen die nog komen. Serveren wat waar is kost hier niets.
+      'shaer:compose': p.composeAllowed,
+      'shaer:replies': p.repliesAllowed,
+      'shaer:messages': p.messagesAllowed,
+      'shaer:images': p.imagesAllowed,
+      'shaer:music': p.musicAllowed,
+      'shaer:quoteCards': p.quotesAllowed,
+      'shaer:customEmoji': p.emojiAllowed,
+      'shaer:externalThreads': p.threadsAllowed,
+      'shaer:following': p.followingAllowed,
+      // Stond in de catalogus mét kolom, en ontbrak hier: de guardian zag de
+      // poort in zijn paneel en de app van het kind heeft er nooit van gehoord.
+      // Gevonden door de pariteitstest, niet door iemand die het toevallig zag.
+      'shaer:accountMove': gate('gate_account_move'),
+    };
 }
 
@@ -780,4 +815,9 @@
       partOf: `${base}/ap/users/${auth.site.slug}/inbox`,
       orderedItems: items,
+      // De rechten gaan MEE. Zonder dit valt de client terug op zijn standaard,
+      // en die standaard is 'alles mag' -- dan zet een gesloten poort zichzelf
+      // stil open bij elke verschil-lezing. Dezelfde reden waarom een 304 de
+      // caps met rust laat.
+      'shaer:capabilities': capabilitiesOf(P, gate),
       'shaer:cursor': AP.feedCursor(auth.site.slug),
     }, 'private, no-store');
@@ -832,29 +872,5 @@
     // by construction, and never on the public actor document: it says
     // something about a child, and only the child and its guardians need it.
-    'shaer:capabilities': {
-      'shaer:externalEmbeds': embedsAllowed,
-      'shaer:externalPlayback': playbackAllowed,
-      // Leaving the app is the same decision as playing inside it: with the
-      // gate shut a link is shown but not followed, so the door is closed too
-      // and not just the picture over it.
-      'shaer:externalLinks': playbackAllowed,
-      // De rest van de familie (8-8): de app hoort VOORAF te weten wat hij mag
-      // aanbieden in plaats van het bij de eerste weigering te ontdekken. De
-      // (+) kaart leest shaer:compose al (Barts gate); de rest is er voor de
-      // schermen die nog komen. Serveren wat waar is kost hier niets.
-      'shaer:compose': composeAllowed,
-      'shaer:replies': repliesAllowed,
-      'shaer:messages': messagesAllowed,
-      'shaer:images': imagesAllowed,
-      'shaer:music': musicAllowed,
-      'shaer:quoteCards': quotesAllowed,
-      'shaer:customEmoji': emojiAllowed,
-      'shaer:externalThreads': threadsAllowed,
-      'shaer:following': followingAllowed,
-      // Stond in de catalogus mét kolom, en ontbrak hier: de guardian zag de
-      // poort in zijn paneel en de app van het kind heeft er nooit van gehoord.
-      // Gevonden door de pariteitstest, niet door iemand die het toevallig zag.
-      'shaer:accountMove': gate('gate_account_move'),
-    },
+    'shaer:capabilities': capabilitiesOf(P, gate),
     // Het merk van wat hierin zit. Geef hem terug als `since` om op het
     // volgende te wachten. NA het samenstellen bepaald, zodat hij precies dekt
Index: test/feed-changes.test.js
===================================================================
--- test/feed-changes.test.js	(revision 21ef238b2aef0be15654f3a397db52757fa168b1)
+++ test/feed-changes.test.js	(revision e2150909dd29bd970c606ecdc6b09f233415d3a8)
@@ -87,4 +87,16 @@
 });
 
+test('het verschil draagt de rechten mee', async () => {
+  // Zonder dit valt de client terug op zijn standaard, en die standaard is
+  // 'alles mag': een gesloten poort zou zichzelf stil openzetten bij elke
+  // verschil-lezing.
+  const vol = await get('');
+  db.prepare("UPDATE sites SET gate_images = 0 WHERE slug = 'kind'").run();
+  db.prepare("INSERT INTO ap_guardianships (slug, other_uri, role, status) VALUES ('kind', 'https://elders/u/oma', 'guardian', 'accepted')").run();
+  post('https://elders/n/9', '<p>na de poort</p>');
+  const delta = await get(`?changes=1&since=${encodeURIComponent(vol.body['shaer:cursor'])}`);
+  assert.equal(delta.body['shaer:capabilities']['shaer:images'], false, 'de dichte poort reist mee');
+});
+
 test('niets veranderd is een leeg verschil, niet een leeg antwoord', async () => {
   const vol = await get('');
Index: test/gates-capabilities-parity.test.js
===================================================================
--- test/gates-capabilities-parity.test.js	(revision 21ef238b2aef0be15654f3a397db52757fa168b1)
+++ test/gates-capabilities-parity.test.js	(revision e2150909dd29bd970c606ecdc6b09f233415d3a8)
@@ -29,9 +29,15 @@
 const SOURCE = 'src/routes/activitypub.js';
 
-/** De sleutels binnen het shaer:capabilities-blok, gelezen uit de bron. */
+/** De sleutels van het rechtenblok, gelezen uit de bron.
+ *
+ *  Het blok woont sinds 10-8 in capabilitiesOf(), omdat de verschil-lezing
+ *  (?changes=1) dezelfde rechten moet dragen: een antwoord zonder rechten laat
+ *  de client terugvallen op zijn standaard, en die standaard is 'alles mag'.
+ *  Deze toets kijkt daarom naar die functie en niet meer naar de plek in de
+ *  route. */
 function capabilityKeys() {
   const src = fs.readFileSync(SOURCE, 'utf8');
-  const start = src.indexOf("'shaer:capabilities': {");
-  assert.notEqual(start, -1, `${SOURCE} heeft nog een shaer:capabilities-blok`);
+  const start = src.indexOf('function capabilitiesOf(');
+  assert.notEqual(start, -1, `${SOURCE} heeft geen capabilitiesOf() meer`);
   const open = src.indexOf('{', start);
   let depth = 0;
