Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision fb30b5d0f27e2c88d8c14739a2d20267e7a655ed)
+++ src/services/ActivityPubService.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
@@ -1965,4 +1965,14 @@
         return { status: 202, url: actorUri };
       }
+      // Shaer "in Orbit" = a real Block (FEP-c648 client side): lands in
+      // ap_blocks, shows in the Block tab, and purges the actor's cached
+      // content. Client-side filtering becomes a cache of this state.
+      case 'Block': {
+        const targetUri = c2sIdOf(object);
+        if (!targetUri) return { status: 400, error: 'missing_object' };
+        const r = await blockTarget(site, targetUri);
+        if (r && r.error) return { status: 400, error: r.error };
+        return { status: 202, url: targetUri };
+      }
       case 'Undo': {
         const inner = object && typeof object === 'object' ? object : null;
@@ -1971,4 +1981,9 @@
         const innerTarget = c2sIdOf(inner && inner.object);
         if (innerType === 'Follow') { await unfollowActor(site, innerTarget); return { status: 202, url: innerTarget }; }
+        if (innerType === 'Block') {
+          if (!innerTarget) return { status: 400, error: 'missing_object' };
+          unblock(site, innerTarget);   // release from Orbit
+          return { status: 202, url: innerTarget };
+        }
         if (innerType === 'Like' || innerType === 'Announce') {
           const kind = innerType === 'Announce' ? 'unboost' : 'unlike';
Index: test/c2s-block.test.js
===================================================================
--- test/c2s-block.test.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
+++ test/c2s-block.test.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
@@ -0,0 +1,44 @@
+// C2S Block / Undo(Block): Shaer "in Orbit" is a real server-side block.
+// The activity lands in ap_blocks (the Block tab) via the same blockTarget
+// the web UI uses; Undo(Block) releases it.
+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;
+
+db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@test', 'x', 'god');
+db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,?)').run('s1', 'me', 'Me', 'u1', 1);
+const site = db.prepare('SELECT * FROM sites WHERE id = ?').get('s1');
+const user = { id: 'u1', username: 'u1' };
+
+const BULLY = 'https://r.test/u/bully';
+
+test('C2S Block lands in ap_blocks (the Block tab)', async () => {
+  const out = await AP.ingestOutboxActivity(site, user, { type: 'Block', object: BULLY });
+  assert.equal(out.status, 202);
+  assert.equal(out.url, BULLY);
+  const rows = AP.listBlocks('me');
+  assert.equal(rows.length, 1);
+  assert.equal(rows[0].target, BULLY);
+  assert.equal(rows[0].kind, 'actor');
+});
+
+test('C2S Undo(Block) releases it again', async () => {
+  const out = await AP.ingestOutboxActivity(site, user, {
+    type: 'Undo',
+    object: { type: 'Block', object: BULLY },
+  });
+  assert.equal(out.status, 202);
+  assert.equal(AP.listBlocks('me').length, 0);
+});
+
+test('Block without an object is a clean 400', async () => {
+  const out = await AP.ingestOutboxActivity(site, user, { type: 'Block' });
+  assert.equal(out.status, 400);
+});
Index: test/c2s-outbox.test.js
===================================================================
--- test/c2s-outbox.test.js	(revision fb30b5d0f27e2c88d8c14739a2d20267e7a655ed)
+++ test/c2s-outbox.test.js	(revision 4c70ecbe5990b354c76af6b01ef4b3d7e937a63a)
@@ -67,5 +67,6 @@
 
 test('Undo of an unknown inner type → 400', async () => {
-  const out = await AP.ingestOutboxActivity(site, user, { type: 'Undo', object: { type: 'Block', object: 'x' } });
+  // Block used to be the unsupported example; it is real now (c2s-block.test).
+  const out = await AP.ingestOutboxActivity(site, user, { type: 'Undo', object: { type: 'Move', object: 'x' } });
   assert.equal(out.status, 400);
   assert.equal(out.error, 'unsupported_undo');
