Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision e6c6e6f74ea19581f6f61944c055f61510b149dd)
+++ src/services/ActivityPubService.js	(revision 155c24e735f57cb57efc79bdb088c83281ecaa1a)
@@ -46,4 +46,7 @@
     // Question stays valid JSON-LD (a strict processor would otherwise drop votersCount).
     votersCount: 'toot:votersCount',
+    // FEP-633c (Guardians): the shaer namespace. helpRequest marks a direct
+    // note as a ward's call for help (spec 5.2.1); ignorable by everyone else.
+    shaer: 'https://ns.klonkt.com/shaer#',
   },
 ];
@@ -259,4 +262,6 @@
         : (post.to_actor ? [post.to_actor] : [PUBLIC]),
       cc: post.visibility === 'direct' ? [] : [PUBLIC, `${meR}/followers`],
+      // FEP-633c 5.2.1: a ward's call for help. Only ever on direct notes.
+      'shaer:helpRequest': (post.visibility === 'direct' && post.help_request) ? true : undefined,
       tag: [
         ...mentionTags(post.content),
@@ -1855,5 +1860,6 @@
             } : null)
             .filter(Boolean);
-          const r = await deliverDirectNote(site, { recipients, text: plain, language: object.language || null, inReplyTo: typeof object.inReplyTo === 'string' ? object.inReplyTo : null, attachments: atts });
+          const help = object['shaer:helpRequest'] === true || object.helpRequest === true;
+          const r = await deliverDirectNote(site, { recipients, text: plain, language: object.language || null, inReplyTo: typeof object.inReplyTo === 'string' ? object.inReplyTo : null, attachments: atts, helpRequest: help });
           if (!r || !r.id) return { status: 502, error: 'direct_failed' };
           return { status: 201, id: r.id, url: `${base}/ap/notes/${r.id}` };
@@ -1971,5 +1977,5 @@
 // The same S2S leg a Mastodon DM takes, so a guardian on any instance
 // receives it as a private mention (the ward call-for-help path).
-export async function deliverDirectNote(site, { recipients, text, language, inReplyTo, attachments }) {
+export async function deliverDirectNote(site, { recipients, text, language, inReplyTo, attachments, helpRequest }) {
   const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
   const list = [...new Set((recipients || []).filter((u) => /^https?:\/\//i.test(String(u || ''))))].slice(0, 8);
@@ -1999,7 +2005,7 @@
     .map((a) => ({ url: a.url, mediaType: String(a.mediaType), name: String(a.name || '').slice(0, 120) }));
   const id = crypto.randomUUID();
-  db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, created_at)
-              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
-    .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)));
+  db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, created_at)
+              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,CURRENT_TIMESTAMP)`)
+    .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0);
   const row = iStmts().getO.get(id);
   const note = buildReplyNote(base, site, row);
