Index: src/services/guardianship/gated.js
===================================================================
--- src/services/guardianship/gated.js	(revision 6eab7e9673764601cadce6d5f98893e9a32f70a9)
+++ src/services/guardianship/gated.js	(revision 88d7c8fc087ab49a2fd3ee63bf184d15d1f0fa31)
@@ -145,4 +145,38 @@
 }
 
+// ── The guardian-side copy (the missing leg of §5.6) ──────────────
+// A proposal addressed to the ward's server reaches only the proposer and the
+// ward. The other guardians never learn it exists, so a threshold of two can
+// never be met and every proposal expires unanswered. The ward's server
+// therefore FORWARDS it, exactly as it forwards a gated follow (§5.3): each
+// guardian stores a copy it can answer, and the answer travels back to the
+// ward, which tallies.
+
+let _rs = null;
+function rstmts() {
+  if (!_rs) {
+    _rs = {
+      ins: db.prepare(`INSERT INTO ap_gated_reviews (id, guardian_slug, ward_uri, ward_inbox, proposer, feature, value)
+                       VALUES (?,?,?,?,?,?,?)
+                       ON CONFLICT(guardian_slug, id) DO UPDATE SET value = excluded.value, ward_inbox = excluded.ward_inbox`),
+      get: db.prepare('SELECT * FROM ap_gated_reviews WHERE guardian_slug = ? AND id = ?'),
+      bySlug: db.prepare('SELECT * FROM ap_gated_reviews WHERE guardian_slug = ? ORDER BY created_at DESC'),
+      del: db.prepare('DELETE FROM ap_gated_reviews WHERE guardian_slug = ? AND id = ?'),
+      delAll: db.prepare('DELETE FROM ap_gated_reviews WHERE id = ?'),
+    };
+  }
+  return _rs;
+}
+
+export function recordGatedReview(guardianSlug, r) {
+  rstmts().ins.run(r.id, guardianSlug, r.wardUri, r.wardInbox || null, r.proposer || null, r.feature, r.value ? 1 : 0);
+  return rstmts().get.get(guardianSlug, r.id);
+}
+export function getGatedReview(guardianSlug, id) { return rstmts().get.get(guardianSlug, id); }
+export function listGatedReviews(guardianSlug) { return rstmts().bySlug.all(guardianSlug); }
+export function removeGatedReview(guardianSlug, id) { rstmts().del.run(guardianSlug, id); }
+/** Drop every guardian's copy once the decision has settled or lapsed. */
+export function clearGatedReviews(id) { rstmts().delAll.run(id); }
+
 export function rememberGatedOffer(offerId, slug, feature, value) {
   try {
@@ -160,3 +194,4 @@
   tallyGatedSetting, thresholdFor, featureColumn, recordGatedVote, gatedProgress, GATED_WINDOW_MS,
   parseGatedSetting, buildGatedOffer, rememberGatedOffer, recallGatedOffer,
+  recordGatedReview, getGatedReview, listGatedReviews, removeGatedReview, clearGatedReviews,
 };
Index: src/services/guardianship/handshake.js
===================================================================
--- src/services/guardianship/handshake.js	(revision 6eab7e9673764601cadce6d5f98893e9a32f70a9)
+++ src/services/guardianship/handshake.js	(revision 88d7c8fc087ab49a2fd3ee63bf184d15d1f0fa31)
@@ -302,10 +302,41 @@
     const gs = gated.parseGatedSetting(activity.object);
     if (gs) {
-      if (gs.ward !== me) return false;                       // not our ward
-      gated.rememberGatedOffer(idOf(activity), site.slug, gs.feature, gs.value);
-      // The proposer's Offer carries its own agreement (§3.1's one-step clause).
-      const r = gated.recordGatedVote(site.slug, gs.feature, actor, gs.value);
-      notify(site.slug, { kind: 'gated_setting', feature: gs.feature, value: gs.value, state: r.state });
-      return true;
+      const offerId = idOf(activity);
+      // ── I am the WARD: record, tally, and forward to the other guardians.
+      if (gs.ward === me) {
+        gated.rememberGatedOffer(offerId, site.slug, gs.feature, gs.value);
+        // The proposer's Offer carries its own agreement (§3.1's one-step clause).
+        const r = gated.recordGatedVote(site.slug, gs.feature, actor, gs.value);
+        // The forward is the leg that was missing. A proposal addressed to the
+        // ward's server reaches only the proposer and the ward; the other
+        // guardians never learn it exists, so a threshold of two can never be
+        // met and every proposal expires unanswered. The ward's server is the
+        // one that knows the authoritative guardian list, which is exactly why
+        // §5.3 forwards a gated follow from here too.
+        if (r.state === 'open') {
+          for (const g of relations.listGuardians(site.slug).map((x) => x.other_uri)) {
+            if (g === actor) continue;   // the proposer already answered
+            deps.deliverTo(site, g, {
+              id: offerId, type: 'Offer', actor, to: [g], object: activity.object,
+            }).catch(() => { /* the delivery queue retries */ });
+          }
+        } else {
+          gated.clearGatedReviews(offerId);   // settled at once: nothing left to ask
+        }
+        notify(site.slug, { kind: 'gated_setting', feature: gs.feature, value: gs.value, state: r.state });
+        return true;
+      }
+      // ── I am one of the GUARDIANS: the forwarded copy. Store it so this
+      //    guardian can answer; the answer goes back to the ward, which tallies.
+      if (relations.getRelation(site.slug, 'guardian', gs.ward)) {
+        const wardDoc = await deps.fetchActor(gs.ward).catch(() => null);
+        gated.recordGatedReview(site.slug, {
+          id: offerId, wardUri: gs.ward, wardInbox: wardDoc && wardDoc.inbox,
+          proposer: actor, feature: gs.feature, value: gs.value,
+        });
+        notify(site.slug, { kind: 'gated_review', feature: gs.feature, value: gs.value, ward: gs.ward });
+        return true;
+      }
+      return false;   // not our ward, and not a ward we guard
     }
     // §3.6.3: a co-guardian proposes releasing a dormant guardian of THIS
