Index: src/services/guardianship/index.js
===================================================================
--- src/services/guardianship/index.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
+++ src/services/guardianship/index.js	(revision fa33214eb34752fafee25fc95942f470701006f8)
@@ -19,8 +19,9 @@
 export { wireDelivery, c2sVisibility, deliverDirectNote } from './delivery.js';
 export { wireHandshake, handleOutbox as handleGuardianshipOutbox, handleInbox as handleGuardianshipInbox, parseRelationship, parseUndoRelationship, endGuardianship } from './handshake.js';
-export { offersCollection, followsCollection, wardsCollection, guardiansCollection } from './queues.js';
+export { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection } from './queues.js';
 export * as availability from './availability.js';
 export { wireAvailability } from './availability.js';
 export * as follows from './follows.js';
+export * as outgoing from './outgoing.js';
 export { listForParty as listOffersForParty, getOffer, findOfferAnywhere } from './offers.js';
 export {
Index: src/services/guardianship/outgoing.js
===================================================================
--- src/services/guardianship/outgoing.js	(revision fa33214eb34752fafee25fc95942f470701006f8)
+++ src/services/guardianship/outgoing.js	(revision fa33214eb34752fafee25fc95942f470701006f8)
@@ -0,0 +1,117 @@
+/**
+ * Guardianship (FEP-633c §5.3, the other direction) — gating a ward's OWN
+ * follows. Bead shaer-p729; the design is in docs/ward-outbound-follows-design.md,
+ * and the spec question it answers is shaer-yeo5.
+ *
+ * The inbound gate in `follows.js` decides who may follow a ward. This one
+ * decides who a ward may follow. Until now that went out unchecked: the
+ * guardians got a note afterwards (1a2f206), which is informing, not gating —
+ * the door is already open by the time the message arrives.
+ *
+ * The rule (Barts besluit): every outgoing follow waits for a guardian, EXCEPT
+ * where the target already follows the ward through the gate. A guardian
+ * already said yes to that person; asking the same question twice only teaches
+ * people to stop reading the question.
+ */
+import db from '../../config/database.js';
+
+let _s = null;
+function stmts() {
+  if (!_s) {
+    _s = {
+      ins: db.prepare(`INSERT OR IGNORE INTO ap_pending_outgoing_follows
+        (id, ward_slug, target_uri, target_inbox, target_name, target_handle, target_icon, quorum, created_at)
+        VALUES (?,?,?,?,?,?,?,?, CURRENT_TIMESTAMP)`),
+      get: db.prepare('SELECT * FROM ap_pending_outgoing_follows WHERE id = ?'),
+      byTarget: db.prepare('SELECT * FROM ap_pending_outgoing_follows WHERE ward_slug = ? AND target_uri = ?'),
+      byWard: db.prepare("SELECT * FROM ap_pending_outgoing_follows WHERE ward_slug = ? AND status = 'pending' ORDER BY created_at DESC"),
+      approve: db.prepare('INSERT OR IGNORE INTO ap_outgoing_follow_approvals (follow_id, guardian_uri, decision, created_at) VALUES (?,?,?,CURRENT_TIMESTAMP)'),
+      answers: db.prepare('SELECT guardian_uri, decision FROM ap_outgoing_follow_approvals WHERE follow_id = ?'),
+      setStatus: db.prepare('UPDATE ap_pending_outgoing_follows SET status = ? WHERE id = ?'),
+      del: db.prepare('DELETE FROM ap_pending_outgoing_follows WHERE id = ?'),
+      delByTarget: db.prepare('DELETE FROM ap_pending_outgoing_follows WHERE ward_slug = ? AND target_uri = ?'),
+      gateApproved: db.prepare('SELECT 1 FROM ap_followers WHERE slug = ? AND actor_uri = ? AND gate_approved = 1'),
+    };
+  }
+  return _s;
+}
+
+/**
+ * Does this target already follow the ward, with a guardian's blessing?
+ *
+ * Only a gate-approved follower counts. A follower a free actor picked up
+ * before it was ever a ward was never seen by a guardian, so following them
+ * back is a new question, not a settled one. (Rows that predate the marker are
+ * grandfathered at migration; see config/database.js.)
+ */
+export function isMutual(wardSlug, targetUri) {
+  return !!stmts().gateApproved.get(wardSlug, targetUri);
+}
+
+/** Record an outgoing follow awaiting guardian approval. */
+export function recordPending(wardSlug, f) {
+  stmts().ins.run(
+    f.id, wardSlug, f.target, f.inbox || null,
+    f.name || null, f.handle || null, f.icon || null, f.quorum || 'any',
+  );
+  return stmts().byTarget.get(wardSlug, f.target);
+}
+
+export function getPending(id) { return stmts().get.get(id); }
+export function findFor(wardSlug, targetUri) { return stmts().byTarget.get(wardSlug, targetUri); }
+
+/** Outgoing follows this ward is waiting on — the guardian's queue. */
+export function listForWard(wardSlug) { return stmts().byWard.all(wardSlug); }
+
+/**
+ * A guardian's answer. Same shape and the same quorum arithmetic as the
+ * inbound gate, so the two directions cannot drift apart in how they count:
+ * a single reject denies outright, approvals accumulate toward the quorum.
+ */
+export function decide(id, guardianUri, decision, guardiansOfWard) {
+  const follow = stmts().get.get(id);
+  if (!follow || follow.status !== 'pending') return { outcome: 'gone', follow };
+  stmts().approve.run(id, guardianUri, decision === 'reject' ? 'reject' : 'approve');
+  const rows = stmts().answers.all(id);
+  if (rows.some((r) => r.decision === 'reject')) {
+    stmts().setStatus.run('denied', id);
+    return { outcome: 'rejected', follow };
+  }
+  const approvers = new Set(rows.filter((r) => r.decision === 'approve').map((r) => r.guardian_uri));
+  const guardians = (guardiansOfWard || []).filter(Boolean);
+  const enough = follow.quorum === 'all'
+    ? guardians.length > 0 && guardians.every((g) => approvers.has(g))
+    : approvers.size >= 1;                                   // 'any' (default)
+  if (enough) {
+    stmts().setStatus.run('approved', id);
+    return { outcome: 'approved', follow };
+  }
+  return { outcome: 'waiting', follow };
+}
+
+/** The ward changed its mind, or blocked the target: the request is gone. */
+export function withdraw(wardSlug, targetUri) { stmts().delByTarget.run(wardSlug, targetUri); }
+export function remove(id) { stmts().del.run(id); }
+
+/** One request as the queue item the Shaer clients parse, mirroring the
+ *  inbound gated-follow item so a dashboard can render both side by side. */
+export function queueItem(o, me) {
+  const rows = stmts().answers.all(o.id);
+  return {
+    id: o.id,
+    type: 'Follow',
+    actor: o.ward_slug,
+    object: o.target_uri,
+    'shaer:direction': 'outgoing',
+    'shaer:target': o.target_uri,
+    'shaer:targetHandle': o.target_handle || undefined,
+    'shaer:quorum': o.quorum || 'any',
+    'shaer:approvals': rows.filter((r) => r.decision === 'approve').length,
+    'shaer:myVote': rows.some((r) => r.guardian_uri === me),
+    published: o.created_at,
+  };
+}
+
+export default {
+  isMutual, recordPending, getPending, findFor, listForWard, decide, withdraw, remove, queueItem,
+};
Index: src/services/guardianship/queues.js
===================================================================
--- src/services/guardianship/queues.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
+++ src/services/guardianship/queues.js	(revision fa33214eb34752fafee25fc95942f470701006f8)
@@ -12,4 +12,5 @@
 import * as relations from './relations.js';
 import * as availability from './availability.js';
+import * as outgoing from './outgoing.js';
 import * as handshake from './handshake.js';
 
@@ -39,4 +40,9 @@
 }
 
+/** §5.3 outbound: this ward's own follow requests, waiting for its guardians. */
+export function outgoingFollowsCollection(id, slug, me) {
+  return collection(id, outgoing.listForWard(slug).map((o) => outgoing.queueItem(o, me)));
+}
+
 /** The guardian's committed wards, with cached handle for display. */
 export function wardsCollection(id, slug) {
@@ -53,3 +59,3 @@
 }
 
-export default { offersCollection, followsCollection, wardsCollection, guardiansCollection };
+export default { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection };
Index: src/services/guardianship/relations.js
===================================================================
--- src/services/guardianship/relations.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
+++ src/services/guardianship/relations.js	(revision fa33214eb34752fafee25fc95942f470701006f8)
@@ -73,4 +73,9 @@
       offers: `${id}/queues/offers`,
       follows: `${id}/queues/follows`,
+      // Both directions of §5.3, kept apart on purpose: a guardian must be able
+      // to tell "someone wants to follow your ward" from "your ward wants to
+      // follow someone". Same mechanism, opposite question, different words in
+      // the interface (shaer-p729).
+      outgoingFollows: `${id}/queues/outgoing-follows`,
       wards: `${id}/queues/wards`,
       guardians: `${id}/queues/guardians`,
