Index: src/services/guardianship/context.js
===================================================================
--- src/services/guardianship/context.js	(revision 30d0e2cad3257e201abc243a96cf44c6e23237ed)
+++ src/services/guardianship/context.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
@@ -24,3 +24,20 @@
 }
 
-export default { SHAER_CONTEXT, GUARDIAN_RELATIONSHIP, GUARDIAN_RELATIONSHIP_COMPACT, isGuardianRelationship };
+/**
+ * True when an actor document carries `shaer:guardians` — i.e. it is a ward,
+ * and therefore not a valid guardian (§1). The one question §4 asks, in both
+ * places it asks it: before committing a guardianship (§4.2) and before
+ * delivering an escalation to one (§4.1).
+ *
+ * §2.1 allows the list as an array of URIs, a single URI, or a Collection, so
+ * all three are read here rather than in each caller.
+ */
+export function carriesGuardians(doc) {
+  const g = doc && doc['shaer:guardians'];
+  if (Array.isArray(g)) return g.length > 0;
+  if (typeof g === 'string') return g.length > 0;
+  if (g && typeof g === 'object') return Array.isArray(g.items) ? g.items.length > 0 : true;
+  return false;
+}
+
+export default { SHAER_CONTEXT, GUARDIAN_RELATIONSHIP, GUARDIAN_RELATIONSHIP_COMPACT, isGuardianRelationship, carriesGuardians };
Index: src/services/guardianship/delivery.js
===================================================================
--- src/services/guardianship/delivery.js	(revision 30d0e2cad3257e201abc243a96cf44c6e23237ed)
+++ src/services/guardianship/delivery.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
@@ -13,4 +13,5 @@
 import crypto from 'crypto';
 import db from '../../config/database.js';
+import { carriesGuardians } from './context.js';
 
 const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
@@ -49,4 +50,5 @@
   // Resolve every recipient for a mention anchor + a delivery inbox.
   const resolved = [];
+  const teapots = [];
   for (const uri of list) {
     // An actor we host is read from our own database, not fetched from our own
@@ -56,7 +58,25 @@
     const a = (localActor && localActor(uri)) || await fetchActor(uri).catch(() => null);
     if (!a || !(a.inbox || (a.endpoints && a.endpoints.sharedInbox))) continue;
+    // FEP-633c §4.1: an escalation addressed to a "guardian" that carries
+    // guardians of its own goes nowhere. There is no grand-guardian, so we
+    // MUST NOT recurse to that actor's guardians — and we fail SOFTLY: drop
+    // this one target and keep delivering to the rest, because a malformed
+    // guardian must never cost a child the guardians who are fine.
+    //
+    // Only for a call for help. An ordinary direct note is not an escalation,
+    // and a ward is perfectly entitled to message another ward.
+    if (helpRequest && carriesGuardians(a)) { teapots.push(uri); continue; }
     resolved.push({ uri, inbox: (a.endpoints && a.endpoints.sharedInbox) || a.inbox, local: !!a.local, handle: deriveHandle(uri), url: a.url || uri });
   }
-  if (!resolved.length) return null;
+  if (teapots.length) console.warn('[AP] not a teapot: escalation dropped for malformed guardian(s)', teapots.join(', '));
+  if (!resolved.length) {
+    // Every guardian was malformed. §4 does not say what to do here because
+    // §4.1 assumes there are others to continue to — but a ward whose whole
+    // safety net is broken has just called for help into nothing, which is the
+    // one outcome this FEP exists to prevent. Say so loudly; the caller can
+    // tell "nobody was reachable" from "nobody was valid".
+    if (teapots.length) console.error('[AP] EVERY guardian of', site.slug, 'is malformed: the call for help reached no one');
+    return null;
+  }
   const mention = resolved.map((r) => {
     const disp = r.handle && r.handle[0] === '@' ? r.handle : '@' + (r.handle || '');
@@ -104,5 +124,5 @@
   }
   console.log('[AP] direct note', site.slug, '→', resolved.length, 'recipient(s), delivered', delivered);
-  return { id, content, delivered };
+  return { id, content, delivered, teapots };
 }
 
Index: src/services/guardianship/handshake.js
===================================================================
--- src/services/guardianship/handshake.js	(revision 30d0e2cad3257e201abc243a96cf44c6e23237ed)
+++ src/services/guardianship/handshake.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
@@ -16,5 +16,5 @@
  * arrive once via wireHandshake(deps); nothing here imports ActivityPubService.
  */
-import { isGuardianRelationship, GUARDIAN_RELATIONSHIP_COMPACT } from './context.js';
+import { isGuardianRelationship, GUARDIAN_RELATIONSHIP_COMPACT, carriesGuardians } from './context.js';
 import * as offers from './offers.js';
 import * as relations from './relations.js';
@@ -139,10 +139,5 @@
   const doc = await deps.fetchActor(candidateUri).catch(() => null);
   if (!doc) return 'unverified';
-  const g = doc['shaer:guardians'];
-  const has = Array.isArray(g) ? g.length > 0
-    : typeof g === 'string' ? g.length > 0
-      : (g && typeof g === 'object') ? (Array.isArray(g.items) ? g.items.length > 0 : true)
-        : false;
-  return has ? 'malformed' : 'ok';
+  return carriesGuardians(doc) ? 'malformed' : 'ok';
 }
 
Index: test/escalation-teapot.test.js
===================================================================
--- test/escalation-teapot.test.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
+++ test/escalation-teapot.test.js	(revision 3d882bdbd7d45a727ffdcb64eacb870baecdb05d)
@@ -0,0 +1,81 @@
+// FEP-633c §4.1 — a malformed guardian must not cost a child the good ones.
+//
+// A "guardian" that carries guardians of its own is not one (§1). When a ward
+// calls for help, an escalation addressed to such an actor goes nowhere: there
+// is no grand-guardian to recurse to. The spec's answer is to fail SOFTLY —
+// drop that one target, keep delivering to the rest — because the alternative
+// is a child whose call for help fails entirely because one adult's account is
+// misconfigured.
+//
+// Klonkt enforced this nowhere until now; the daemon has had it since the
+// beginning, which is the divergence shaer-6d9 exists to catch.
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+
+process.env.DATABASE_PATH = ':memory:';
+process.env.PUBLIC_BASE_URL = 'https://test.example';
+
+const dbMod = await import('../src/config/database.js');
+const db = dbMod.default;
+dbMod.initializeDatabase();
+const AP = (await import('../src/services/ActivityPubService.js')).default;
+
+const BASE = 'https://test.example';
+const local = (slug) => `${BASE}/ap/users/${slug}`;
+
+db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
+  .run('u1', 'u1', 'u1@test', 'x', 'god');
+let n = 0;
+function site(slug) {
+  db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,?)')
+    .run(`s${++n}`, slug, slug, 'u1', n === 1 ? 1 : 0);
+  return db.prepare('SELECT * FROM sites WHERE slug = ?').get(slug);
+}
+const guards = (slug, other) =>
+  db.prepare("INSERT INTO ap_guardianships (slug, role, other_uri, status) VALUES (?, 'ward', ?, 'accepted')")
+    .run(slug, other);
+
+const kid = site('kid');       // the ward calling for help
+site('good');                  // a proper guardian
+site('bad');                   // listed as a guardian, but a ward itself
+site('gran');                  // who guards `bad`
+
+guards('kid', local('good'));
+guards('kid', local('bad'));
+guards('bad', local('gran'));  // this is what makes `bad` malformed as a guardian
+
+const help = await AP.deliverDirectNote(kid, {
+  recipients: [local('good'), local('bad')],
+  text: 'ik snap dit niet', helpRequest: true,
+});
+
+test('an escalation skips the malformed guardian and still reaches the others (§4.1)', () => {
+  assert.ok(help && help.id, 'the note was built');
+  assert.deepEqual(help.teapots, [local('bad')],
+    'the dropped target is named — SHOULD log the condition, not swallow it');
+  assert.equal(help.delivered, 1,
+    'and the well-formed guardian still got it: a malformed target MUST NOT block the others');
+});
+
+test('an ordinary direct note is not an escalation, so nobody is dropped', async () => {
+  // §4.1 is about escalations. A ward messaging another ward is ordinary
+  // conversation, and silently dropping recipients from it would be a bug
+  // wearing a spec reference.
+  const chat = await AP.deliverDirectNote(kid, { recipients: [local('bad')], text: 'hoi' });
+  assert.ok(chat && chat.id);
+  assert.deepEqual(chat.teapots, []);
+  assert.equal(chat.delivered, 1, 'delivered to a ward, because that is allowed');
+});
+
+test('a ward whose every guardian is malformed calls out into nothing', async () => {
+  // §4 does not cover this, because §4.1 assumes there are others to continue
+  // to. There are not. The call reaches no one, which is the single outcome
+  // this FEP exists to prevent — so it fails loudly in the log rather than
+  // reporting a delivery that did not happen.
+  const orphan = site('orphan');
+  guards('orphan', local('bad'));
+  const nowhere = await AP.deliverDirectNote(orphan, {
+    recipients: [local('bad')], text: 'help', helpRequest: true,
+  });
+  assert.equal(nowhere, null, 'no note, no false "delivered"');
+});
