Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision a7bcf669de39441a2bbc7c41545fc9e17db4b233)
+++ src/services/ActivityPubService.js	(revision 6c152a5a2b3de8369604ab7ceccccad68732ae88)
@@ -1376,5 +1376,8 @@
   // Accept/Reject answers an offer a local guardian sent. Anything the
   // guardianship module does not recognize falls through to the old paths.
-  if (type === 'Offer' || type === 'Accept' || type === 'Reject') {
+  // An Undo of the guardianship Relationship (§3.2) is handled here too, and it
+  // must be seen BEFORE the generic Undo branch below, which only knows about
+  // Follow/Like/Announce and would swallow it with a 202.
+  if (type === 'Offer' || type === 'Accept' || type === 'Reject' || (type === 'Undo' && Guardianship.parseUndoRelationship(act))) {
     // Every LOCAL party this activity is addressed to gets its own copy of the
     // handshake (a ward and a co-guardian may both live here). Gather candidate
@@ -1385,6 +1388,6 @@
       if (typeof t === 'string') { const s = slugFromActorUrl(t); if (s) cand.add(s); }
     }
-    if (type === 'Offer') {
-      const rel = Guardianship.parseRelationship(act.object);
+    if (type === 'Offer' || type === 'Undo') {
+      const rel = type === 'Undo' ? Guardianship.parseUndoRelationship(act) : Guardianship.parseRelationship(act.object);
       if (rel) { const s = slugFromActorUrl(rel.ward); if (s) cand.add(s); }
     }
@@ -3769,8 +3772,12 @@
       offer_for_ward: ['push.n_guard_cog_t', 'push.n_guard_cog_b'],       // I co-guard this ward
       committed: ['push.n_guard_ward_t', 'push.n_guard_ward_b'],
+      // §3.2: a guardian ended the relation. The ward hears that someone who
+      // was looking after them has gone; a co-guardian hears they are one fewer.
+      guardian_left: ['push.n_guard_left_t', 'push.n_guard_left_b'],
+      coguardian_left: ['push.n_guard_cogleft_t', 'push.n_guard_cogleft_b'],
     }[ev.kind];
     if (!texts) return;
-    const who = deriveHandle(ev.candidate || ev.ward || ev.guardian || '') || '?';
-    const url = ev.kind === 'offer_received' ? `${pushPrefix(slug)}/messages` : '/guardian';
+    const who = deriveHandle(ev.candidate || ev.guardian || ev.ward || '') || '?';
+    const url = (ev.kind === 'offer_received' || ev.kind === 'guardian_left') ? `${pushPrefix(slug)}/messages` : '/guardian';
     pushEvent(slug, { type: 'guardian', title: i18nT(L, texts[0]), body: i18nT(L, texts[1], { who }), url });
   },
