Index: src/routes/activitypub.js
===================================================================
--- src/routes/activitypub.js	(revision f06ed56e637b1e0d99567688ceb0281687dff6f3)
+++ src/routes/activitypub.js	(revision 7e53594562c219f7ca4b824fb1257c4526b43783)
@@ -344,4 +344,19 @@
 // guardians' real states here and nowhere else.
 queueRoute('guardians', (id, slug) => Guardianship.guardiansCollection(id, slug));
+
+// ── Het logboek (FEP-633c §4.2, shaer:log) ────────────────────────────
+// NAAST de wachtrijen en niet erin: alles onder shaer:queues wacht op een
+// antwoord, dit is wat er al besloten is. Eigen pad, dezelfde eigenaar-only
+// bearer. Het bestaat omdat een weigering anders alleen te merken viel doordat
+// er iets uit een lijst verdween, en "het is weg" is geen reden.
+router.get('/ap/users/:slug/log', (req, res) => {
+  const auth = OAuth.verifyBearer(req.headers.authorization);
+  if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end();
+  const me = `${baseUrl(req)}/ap/users/${auth.site.slug}`;
+  AP.sendAP(res, {
+    '@context': AP.AP_CONTEXT,
+    ...Guardianship.logCollection(`${me}/log`, auth.site.slug, (s) => AP.listGuardianEvents(s, 50)),
+  }, 'private, no-store');
+});
 // De hulpvragen MET hun staat (5.2.1, shaer-lgo). De apps lazen ze uit de feed
 // en wisten dus niet of er al iemand op af was -- daarom bleef een afgehandeld
Index: src/services/guardianship/index.js
===================================================================
--- src/services/guardianship/index.js	(revision f06ed56e637b1e0d99567688ceb0281687dff6f3)
+++ src/services/guardianship/index.js	(revision 7e53594562c219f7ca4b824fb1257c4526b43783)
@@ -19,5 +19,5 @@
 export { wireDelivery, c2sVisibility, deliverDirectNote } from './delivery.js';
 export { wireHandshake, handleOutbox as handleGuardianshipOutbox, handleInbox as handleGuardianshipInbox, parseRelationship, parseUndoRelationship, endGuardianship } from './handshake.js';
-export { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection, helpCollection } from './queues.js';
+export { offersCollection, followsCollection, outgoingFollowsCollection, logCollection, wardsCollection, guardiansCollection, helpCollection } from './queues.js';
 export * as availability from './availability.js';
 export { wireAvailability } from './availability.js';
Index: src/services/guardianship/queues.js
===================================================================
--- src/services/guardianship/queues.js	(revision f06ed56e637b1e0d99567688ceb0281687dff6f3)
+++ src/services/guardianship/queues.js	(revision 7e53594562c219f7ca4b824fb1257c4526b43783)
@@ -118,5 +118,32 @@
 }
 
-export default { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection, helpCollection, helpItemsFor, wardGates, wardGuardianStatuses };
+/**
+ * Het logboek (§4.2): wat er is besloten, en waarom.
+ *
+ * Geen wachtrij, en daarom een eigen sleutel op de actor. Elk item draagt zijn
+ * soort en, als die er was, de REDEN -- want zonder die reden merkte een ward
+ * een weigering alleen doordat er iets uit een lijst verdween.
+ *
+ * `type` is geen AS2-werkwoord: de meeste soorten zijn er geen. Een lapse-stem
+ * of een opgepakte hulpvraag is geen Accept, en het zo noemen zou netter lezen
+ * dan het is.
+ *
+ * De lezer levert `listEvents` aan; deze module kent ActivityPubService niet en
+ * houdt dat zo (zie de kop van delivery.js).
+ */
+export function logCollection(id, slug, listEvents) {
+  const items = (typeof listEvents === 'function' ? listEvents(slug) : []).map((e) => {
+    const { id: n, kind, created, ...rest } = e;
+    const item = { id: `${id}/${n}`, type: 'shaer:Event', 'shaer:kind': kind, published: created };
+    for (const [k, v] of Object.entries(rest)) {
+      if (v === undefined || v === null) continue;
+      item[k.startsWith('shaer:') ? k : `shaer:${k}`] = v;
+    }
+    return item;
+  });
+  return collection(id, items);
+}
+
+export default { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection, helpCollection, helpItemsFor, wardGates, wardGuardianStatuses, logCollection };
 
 // ── Wat er voor een ward gated is (shaer-ahy.1) ─────────────────────────
Index: src/services/guardianship/relations.js
===================================================================
--- src/services/guardianship/relations.js	(revision f06ed56e637b1e0d99567688ceb0281687dff6f3)
+++ src/services/guardianship/relations.js	(revision 7e53594562c219f7ca4b824fb1257c4526b43783)
@@ -82,4 +82,10 @@
       help: `${id}/queues/help`,
     },
+    // NAAST de wachtrijen, niet erin. Alles onder shaer:queues wacht op een
+    // antwoord; dit is wat er al besloten is, met de reden erbij (§4.2).
+    // Geschiedenis onderbrengen bij een woord dat "wachtend" betekent maakt van
+    // twee dingen één, en dat is precies de fout die de rest van deze module
+    // net heeft opgeruimd.
+    'shaer:log': `${id}/log`,
   };
   const guardians = listGuardians(slug).map((r) => r.other_uri);
Index: test/activitypub-as2.test.js
===================================================================
--- test/activitypub-as2.test.js	(revision f06ed56e637b1e0d99567688ceb0281687dff6f3)
+++ test/activitypub-as2.test.js	(revision 7e53594562c219f7ca4b824fb1257c4526b43783)
@@ -45,5 +45,7 @@
   // `help` is de vragenlijst met haar STAAT (5.2.1): de apps lazen hulpvragen uit
   // de feed en wisten niet of er al iemand op af was.
-  'shaer:queues', 'offers', 'follows', 'outgoingFollows', 'wards', 'guardians', 'help',
+  'shaer:queues', 'offers', 'follows', 'outgoingFollows', 'wards', 'guardians',
+  // §4.2: het logboek staat NAAST de wachtrijen, want het wacht nergens op.
+  'shaer:log', 'help',
   // Elke OPEN hulpvraag zit in de collectie; daarom mag een app uit afwezigheid
   // concluderen dat iets niet open is (shaer-6wt).
Index: test/guardian-event-log.test.js
===================================================================
--- test/guardian-event-log.test.js	(revision f06ed56e637b1e0d99567688ceb0281687dff6f3)
+++ test/guardian-event-log.test.js	(revision 7e53594562c219f7ca4b824fb1257c4526b43783)
@@ -76,2 +76,38 @@
   assert.doesNotThrow(() => AP.onGuardianshipEvent(null, { kind: 'setting' }));
 });
+
+// ── De collectie die de apps lezen (shaer:log) ────────────────────────────
+// Het logboek staat NAAST shaer:queues. Alles daarin wacht op een antwoord; dit
+// is wat er al besloten is. Een aparte sleutel dus, en een aparte vorm.
+
+test('de actor wijst het logboek aan, los van de wachtrijen', async () => {
+  const G = await import('../src/services/guardianship/index.js');
+  const props = G.guardianshipActorProps('https://test.example/ap/users/kid', 'kid');
+  assert.equal(props['shaer:log'], 'https://test.example/ap/users/kid/log');
+  assert.equal(props['shaer:queues'].log, undefined,
+    'geen geschiedenis onder een woord dat "wachtend" betekent');
+});
+
+test('elk item draagt zijn soort en zijn reden mee', async () => {
+  const G = await import('../src/services/guardianship/index.js');
+  const c = G.logCollection('https://test.example/ap/users/kid/log', 'kid',
+    (s) => AP.listGuardianEvents(s, 50));
+  assert.equal(c.type, 'OrderedCollection');
+  const weigering = c.orderedItems.find((i) => i['shaer:kind'] === 'offer_rejected');
+  assert.ok(weigering, 'de weigering staat erin');
+  assert.equal(weigering['shaer:reason'], 'not_a_teapot',
+    'MET de reden: daarvoor bestaat dit ding');
+  assert.equal(weigering['shaer:candidate'], 'https://elders.example/users/tess');
+  assert.ok(weigering.published, 'en wanneer');
+  // Geen AS2-werkwoord: een lapse-stem of een opgepakte hulpvraag is geen
+  // Accept, en het zo noemen zou netter lezen dan het is.
+  assert.equal(weigering.type, 'shaer:Event');
+});
+
+test('een leeg logboek is een lege collectie, geen fout', async () => {
+  const G = await import('../src/services/guardianship/index.js');
+  const c = G.logCollection('https://test.example/ap/users/niemand/log', 'niemand',
+    (s) => AP.listGuardianEvents(s, 50));
+  assert.equal(c.totalItems, 0);
+  assert.deepEqual(c.orderedItems, []);
+});
