Index: src/routes/guardian.js
===================================================================
--- src/routes/guardian.js	(revision 65abc8569b0cf766ad2c809114e7f385c1aff3b8)
+++ src/routes/guardian.js	(revision 2708282985faa68002d3d9051b403c0e12b11dd9)
@@ -317,5 +317,5 @@
     return res.json({ ok: true, allow, state: r.state, need: r.need, of: r.of });
   }
-  AP.deliverTo(site, uri, offer).catch(() => { /* queued, best-effort */ });
+  AP.deliverToActor(site, uri, offer).catch(() => { /* queued, best-effort */ });
   res.json({ ok: true, allow, state: 'open', federated: true });
 });
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 65abc8569b0cf766ad2c809114e7f385c1aff3b8)
+++ src/services/ActivityPubService.js	(revision 2708282985faa68002d3d9051b403c0e12b11dd9)
@@ -3647,5 +3647,5 @@
 // { delivered, inbox }: delivered=false means the account could not be
 // resolved at all (a bad handle) — the offer stays recorded regardless.
-async function deliverToActor(site, actorUri, activity) {
+export async function deliverToActor(site, actorUri, activity) {
   const me = selfActorId(site.slug);
   const keys = getOrCreateKeys(site.slug);
@@ -3708,5 +3708,5 @@
   getInteractions, getInteractionById, setInteractionBoosted, setInteractionLiked, setMyReaction, getMyReactions, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
   listOutbox, deliverOutboxDelete, deliverOutboxUpdate, deliverDirectNote,
-  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineAttachments, timelineEmojis, timelineObjectLinks, timelineQuote, timelineEmbed, applyQuoteProps, sendInteraction, voteOnPoll, voteOnRemotePoll,
+  webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineAttachments, timelineEmojis, timelineObjectLinks, timelineQuote, timelineEmbed, applyQuoteProps, deliverToActor, sendInteraction, voteOnPoll, voteOnRemotePoll,
   acceptGatedFollow, rejectGatedFollow, isWardGuardian, sendFollowDecision,
   parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs,
Index: test/gated-settings.test.js
===================================================================
--- test/gated-settings.test.js	(revision 65abc8569b0cf766ad2c809114e7f385c1aff3b8)
+++ test/gated-settings.test.js	(revision 2708282985faa68002d3d9051b403c0e12b11dd9)
@@ -61,2 +61,23 @@
   assert.equal(featureColumn('external_embeds = 1; DROP TABLE sites'), null);
 });
+
+// Guard against the mistake that made the button do nothing: the route called
+// AP.deliverTo, which existed only as a key in the guardianship deps object and
+// not as an export. It threw a TypeError, Express answered 500, and the button
+// silently reset. A grep hit is not an export.
+test('the guardian route can reach every ActivityPub helper it calls', async () => {
+  const AP = (await import('../src/services/ActivityPubService.js')).default;
+  for (const fn of ['actorId', 'deliverToActor', 'followActor', 'backfillFromOutbox', 'getTimeline']) {
+    assert.equal(typeof AP[fn], 'function', `AP.${fn} must be exported, the guardian route calls it`);
+  }
+});
+
+test('a gated-setting Offer carries ward, feature and value', async () => {
+  const { buildGatedOffer, parseGatedSetting } = await import('../src/services/guardianship/gated.js');
+  const o = buildGatedOffer('https://a/gated/1', 'https://a/g1', 'https://b/ward', 'shaer:externalEmbeds', true);
+  assert.equal(o.type, 'Offer');
+  assert.deepEqual(o.to, ['https://b/ward'], 'addressed to the ward server, which tallies');
+  const parsed = parseGatedSetting(o.object);
+  assert.deepEqual(parsed, { ward: 'https://b/ward', feature: 'shaer:externalEmbeds', value: true });
+  assert.equal(parseGatedSetting({ type: 'Relationship' }), null, 'a different Offer is not ours');
+});
