Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision ad6f62af71f052ed32116666c62fefa7a06be1ac)
+++ src/services/ActivityPubService.js	(revision c66cbb4536b30530330e60aecad2a6de103f0375)
@@ -144,7 +144,8 @@
 
 const AP_CONTENT_TYPE = 'application/activity+json; charset=utf-8';
-export function sendAP(res, obj) {
+export function sendAP(res, obj, cacheControl) {
   res.type(AP_CONTENT_TYPE);
-  res.set('Cache-Control', 'public, max-age=120');
+  // A per-caller (e.g. guardian-widened) view must not be publicly cached.
+  res.set('Cache-Control', cacheControl || 'public, max-age=120');
   res.send(JSON.stringify(obj));
 }
@@ -2901,4 +2902,11 @@
 }
 
+// FEP-633c §5.3 note (authorized fetch): true when `actorUri` is a committed
+// guardian of the local ward `wardSlug` — so a signed GET from it may read the
+// ward's non-public history without the guardian appearing as a follower.
+export function isWardGuardian(wardSlug, actorUri) {
+  try { return !!Guardianship.getRelation(wardSlug, 'ward', actorUri); } catch { return false; }
+}
+
 // FEP-633c §5.3: the guardians approved a gated follow of their ward. Send the
 // Accept to the follower and record them, so delivery (incl. followers-only)
@@ -3248,5 +3256,5 @@
   listOutbox, deliverOutboxDelete, deliverOutboxUpdate, deliverDirectNote,
   webfingerResolve, followActor, resolveRemoteActor, unfollowActor, listFollowing, setAutoBoost, backfillFromOutbox, getTimeline, timelineAttachments, sendInteraction, voteOnPoll, voteOnRemotePoll,
-  acceptGatedFollow, rejectGatedFollow,
+  acceptGatedFollow, rejectGatedFollow, isWardGuardian,
   parseOwnPoll, pollTally, ownPollView, deliverPollUpdate, maybeCrawlThread, sendReport, localMentionSlugs,
   autoBoostCount, boostedCount, markBoosted, unmarkBoosted, markLiked, unmarkLiked, getTimelineReaction, upsertBoostedNote, getCirkelPosts, getCirkelMembers, selfHealTimeline,
