Index: src/config/database.js
===================================================================
--- src/config/database.js	(revision e62eee4b3ba1595f02ee56042ffbb72b7e0fb115)
+++ src/config/database.js	(revision 709dc6fc92eb30af5384833e3f355b2d62423768)
@@ -177,4 +177,10 @@
   ensureColumn('sites', 'gate_custom_emoji', 'INTEGER');  // FEP-9098 emoji-plaatjes (shaer-ytw)
   ensureColumn('sites', 'gate_account_move', 'INTEGER');  // FEP-7628 Move (shaer-tge)
+  // Wie de ward ZELF mag volgen (shaer-p729): de tegenhanger van shaer:follows,
+  // dat over de andere richting gaat. §5.3 schrijft alleen het doorsturen voor
+  // van een Follow NAAR een ward; wat je verder gated is een keuze van de
+  // implementatie, en dit is die keuze. Verstelbaar, anders dan de inkomende
+  // kant: een kind dat ouder wordt hoort niet eeuwig te blijven vragen.
+  ensureColumn('sites', 'gate_following', 'INTEGER');     // zelf iemand volgen (shaer-p729)
   // The heavier sibling (FEP-633c 5.6): may a player from outside this app run
   // INSIDE it? A preview is a picture; playback hands the screen to a third
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision e62eee4b3ba1595f02ee56042ffbb72b7e0fb115)
+++ src/services/ActivityPubService.js	(revision 709dc6fc92eb30af5384833e3f355b2d62423768)
@@ -5476,4 +5476,12 @@
   const guardians = Guardianship.listGuardians(slug).map((g) => g.other_uri);
   if (!guardians.length) return null;                                   // not a ward: nothing to gate
+  // shaer:following (shaer-p729) — its own gate, apart from shaer:follows,
+  // which governs the OTHER direction. §5.3 fixes the inbound one on: a Follow
+  // aimed at a ward MUST pass the guardians. About this direction the FEP says
+  // nothing, so it is ours to set and ours to let go of, and the guardians can
+  // relax it for a child who has grown into it. Undecided means gated for a
+  // ward, the same automatiek as the rest of the family.
+  const gateRow = db.prepare('SELECT gate_following FROM sites WHERE slug = ?').get(slug);
+  if (Guardianship.wardGateAllowed(gateRow && gateRow.gate_following, true)) return null;
   if (guardians.includes(targetUri)) return null;                       // your own guardian
   if (Guardianship.outgoing.isMutual(slug, targetUri)) return null;     // already vetted by name
Index: src/services/guardianship/gated.js
===================================================================
--- src/services/guardianship/gated.js	(revision e62eee4b3ba1595f02ee56042ffbb72b7e0fb115)
+++ src/services/guardianship/gated.js	(revision 709dc6fc92eb30af5384833e3f355b2d62423768)
@@ -68,4 +68,5 @@
   'shaer:customEmoji': 'gate_custom_emoji',
   'shaer:accountMove': 'gate_account_move',
+  'shaer:following': 'gate_following',
 };
 /**
@@ -112,7 +113,20 @@
   { feature: 'shaer:customEmoji', kind: 'setting', reversible: true, bead: 'shaer-ytw' },
   { feature: 'shaer:accountMove', kind: 'setting', reversible: true, bead: 'shaer-tge' },
-  // Altijd aan voor een ward (5.3): niet te verzetten, wel te tonen. Een paneel
-  // dat alleen verstelbare dingen laat zien verzwijgt de helft van wat er geldt.
+  // Wie de ward mag VOLGEN, en wie de ward mag volgen: twee poorten, want twee
+  // vragen. Ze stonden hier als één rij, en dan telt het paneel de ene richting
+  // en zwijgt over de andere -- een guardian ziet "follows: 3 wachtend" en weet
+  // niet of er drie vreemden bij zijn kind willen of dat zijn kind drie keer
+  // heeft gevraagd of het iemand mag volgen. Dat zijn niet dezelfde zorg.
+  //
+  // Inkomend is vast: §5.3 EIST dat een Follow naar een ward langs de guardians
+  // gaat, dus die staat aan en blijft aanstaan. Tonen mag, verzetten niet.
   { feature: 'shaer:follows', kind: 'perRequest', reversible: true, fixed: true },
+  // Uitgaand is verstelbaar, en dat verschil is opzet. De FEP zegt over deze
+  // richting niets: §5.3 gaat alleen over een Follow die op een ward AF komt.
+  // Wat je verder gated is expliciet aan de implementatie gelaten, dus dit is
+  // onze keuze en niet die van de spec -- en dan hoort hij ook echt te kunnen
+  // worden losgelaten, want een kind dat ouder wordt hoort niet eeuwig te
+  // blijven vragen wie het mag volgen (shaer-p729, shaer-yeo5).
+  { feature: 'shaer:following', kind: 'perRequest', reversible: true, bead: 'shaer-p729' },
 
   // GEPLAND, en dat is bij deze twee geen achterstand maar een besluit.
Index: src/services/guardianship/queues.js
===================================================================
--- src/services/guardianship/queues.js	(revision e62eee4b3ba1595f02ee56042ffbb72b7e0fb115)
+++ src/services/guardianship/queues.js	(revision 709dc6fc92eb30af5384833e3f355b2d62423768)
@@ -158,5 +158,10 @@
 export function wardGates(mySlug, wardUri) {
   const statuses = wardGuardianStatuses(wardUri);
-  const wachtend = follows.listReviewsByDirection(mySlug, 'incoming')
+  // Per richting geteld, want het zijn twee zorgen. "follows: 3 wachtend" liet
+  // een guardian niet zien of er drie vreemden bij zijn kind willen of dat zijn
+  // kind drie keer heeft gevraagd of het iemand mag volgen (shaer-p729).
+  const wachtendIn = follows.listReviewsByDirection(mySlug, 'incoming')
+    .filter((r) => r.ward_uri === wardUri).length;
+  const wachtendUit = follows.listReviewsByDirection(mySlug, 'outgoing')
     .filter((r) => r.ward_uri === wardUri).length;
   return gated.gateRows({
@@ -172,5 +177,8 @@
       feature: p.feature, value: !!p.value, status: gated.sentStatus(p, Date.now()),
     })),
-    waiting: { 'shaer:follows': wachtend || undefined },
+    waiting: {
+      'shaer:follows': wachtendIn || undefined,
+      'shaer:following': wachtendUit || undefined,
+    },
     // De vraag van het kind zelf staat APART van wat er in een wachtrij staat
     // (shaer-8ru). Allebei "n waiting" noemen maakt van twee verschillende
Index: test/outgoing-follow-gate.test.js
===================================================================
--- test/outgoing-follow-gate.test.js	(revision e62eee4b3ba1595f02ee56042ffbb72b7e0fb115)
+++ test/outgoing-follow-gate.test.js	(revision 709dc6fc92eb30af5384833e3f355b2d62423768)
@@ -152,2 +152,47 @@
   assert.notEqual(uit && uit.held, true);
 });
+
+// ── shaer:following als eigen poort (shaer-p729) ──────────────────────────
+// De uitgaande kant stond als één rij met de inkomende in het paneel, en dan
+// telt een guardian de ene richting en hoort niets over de andere. Nu twee
+// poorten. Het VERSCHIL tussen die twee is opzet: §5.3 eist dat een Follow
+// NAAR een ward langs de guardians gaat, dus die staat vast aan. Over deze
+// richting zegt de FEP niets, dus die is van ons -- en dan hoort hij ook echt
+// losgelaten te kunnen worden.
+
+test('onbeslist betekent dicht voor een ward, net als bij de andere poorten', async () => {
+  const wim = site('wim');
+  guards('wim', local('mum'));
+  const held = await AP.gateOutgoingFollow(wim, 'https://elders.example/users/nieuw');
+  assert.ok(held, 'niemand heeft er iets over besloten, dus vragen we');
+});
+
+test('de guardians kunnen de poort openzetten, en dan vraagt het kind niets meer', async () => {
+  const zoe = site('zoe');
+  guards('zoe', local('mum'));
+  db.prepare('UPDATE sites SET gate_following = 1 WHERE slug = ?').run('zoe');
+  const zoeSite = db.prepare('SELECT * FROM sites WHERE slug = ?').get('zoe');
+  assert.equal(await AP.gateOutgoingFollow(zoeSite, 'https://elders.example/users/nieuw'), null,
+    'een kind dat erin gegroeid is hoeft niet eeuwig te blijven vragen');
+});
+
+test('en weer dicht is ook een besluit', async () => {
+  const zoeSite = db.prepare('SELECT * FROM sites WHERE slug = ?').get('zoe');
+  db.prepare('UPDATE sites SET gate_following = 0 WHERE slug = ?').run('zoe');
+  const dicht = db.prepare('SELECT * FROM sites WHERE slug = ?').get('zoe');
+  assert.ok(await AP.gateOutgoingFollow(dicht, 'https://elders.example/users/weer'),
+    'terugdraaien kan: de poort is reversible');
+  assert.equal(zoeSite.gate_following, 1, '(en de oude rij was echt open)');
+});
+
+test('de twee richtingen tellen apart in het paneel', async () => {
+  const queues = await import('../src/services/guardianship/queues.js');
+  const rows = queues.wardGates('mum', local('kid'));
+  const namen = rows.map((r) => r.feature);
+  assert.ok(namen.includes('shaer:follows'), 'wie het kind wil volgen');
+  assert.ok(namen.includes('shaer:following'), 'en wie het kind wil volgen -- andersom');
+  const uit = rows.find((r) => r.feature === 'shaer:following');
+  assert.equal(uit.adjustable, true, 'deze mag verzet worden');
+  const inn = rows.find((r) => r.feature === 'shaer:follows');
+  assert.equal(inn.adjustable, false, 'en deze niet: §5.3 laat er geen ruimte voor');
+});
