Changeset 3d882bd in Klonkt for src/services
- Timestamp:
- 08/04/2026 12:30:11 PM (5 weeks ago)
- Branches:
- main
- Children:
- fa33214
- Parents:
- 30d0e2c
- Location:
- src/services/guardianship
- Files:
-
- 3 edited
-
context.js (modified) (1 diff)
-
delivery.js (modified) (4 diffs)
-
handshake.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/guardianship/context.js
r30d0e2c r3d882bd 24 24 } 25 25 26 export default { SHAER_CONTEXT, GUARDIAN_RELATIONSHIP, GUARDIAN_RELATIONSHIP_COMPACT, isGuardianRelationship }; 26 /** 27 * True when an actor document carries `shaer:guardians` — i.e. it is a ward, 28 * and therefore not a valid guardian (§1). The one question §4 asks, in both 29 * places it asks it: before committing a guardianship (§4.2) and before 30 * delivering an escalation to one (§4.1). 31 * 32 * §2.1 allows the list as an array of URIs, a single URI, or a Collection, so 33 * all three are read here rather than in each caller. 34 */ 35 export function carriesGuardians(doc) { 36 const g = doc && doc['shaer:guardians']; 37 if (Array.isArray(g)) return g.length > 0; 38 if (typeof g === 'string') return g.length > 0; 39 if (g && typeof g === 'object') return Array.isArray(g.items) ? g.items.length > 0 : true; 40 return false; 41 } 42 43 export default { SHAER_CONTEXT, GUARDIAN_RELATIONSHIP, GUARDIAN_RELATIONSHIP_COMPACT, isGuardianRelationship, carriesGuardians }; -
src/services/guardianship/delivery.js
r30d0e2c r3d882bd 13 13 import crypto from 'crypto'; 14 14 import db from '../../config/database.js'; 15 import { carriesGuardians } from './context.js'; 15 16 16 17 const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; … … 49 50 // Resolve every recipient for a mention anchor + a delivery inbox. 50 51 const resolved = []; 52 const teapots = []; 51 53 for (const uri of list) { 52 54 // An actor we host is read from our own database, not fetched from our own … … 56 58 const a = (localActor && localActor(uri)) || await fetchActor(uri).catch(() => null); 57 59 if (!a || !(a.inbox || (a.endpoints && a.endpoints.sharedInbox))) continue; 60 // FEP-633c §4.1: an escalation addressed to a "guardian" that carries 61 // guardians of its own goes nowhere. There is no grand-guardian, so we 62 // MUST NOT recurse to that actor's guardians — and we fail SOFTLY: drop 63 // this one target and keep delivering to the rest, because a malformed 64 // guardian must never cost a child the guardians who are fine. 65 // 66 // Only for a call for help. An ordinary direct note is not an escalation, 67 // and a ward is perfectly entitled to message another ward. 68 if (helpRequest && carriesGuardians(a)) { teapots.push(uri); continue; } 58 69 resolved.push({ uri, inbox: (a.endpoints && a.endpoints.sharedInbox) || a.inbox, local: !!a.local, handle: deriveHandle(uri), url: a.url || uri }); 59 70 } 60 if (!resolved.length) return null; 71 if (teapots.length) console.warn('[AP] not a teapot: escalation dropped for malformed guardian(s)', teapots.join(', ')); 72 if (!resolved.length) { 73 // Every guardian was malformed. §4 does not say what to do here because 74 // §4.1 assumes there are others to continue to — but a ward whose whole 75 // safety net is broken has just called for help into nothing, which is the 76 // one outcome this FEP exists to prevent. Say so loudly; the caller can 77 // tell "nobody was reachable" from "nobody was valid". 78 if (teapots.length) console.error('[AP] EVERY guardian of', site.slug, 'is malformed: the call for help reached no one'); 79 return null; 80 } 61 81 const mention = resolved.map((r) => { 62 82 const disp = r.handle && r.handle[0] === '@' ? r.handle : '@' + (r.handle || ''); … … 104 124 } 105 125 console.log('[AP] direct note', site.slug, '→', resolved.length, 'recipient(s), delivered', delivered); 106 return { id, content, delivered };126 return { id, content, delivered, teapots }; 107 127 } 108 128 -
src/services/guardianship/handshake.js
r30d0e2c r3d882bd 16 16 * arrive once via wireHandshake(deps); nothing here imports ActivityPubService. 17 17 */ 18 import { isGuardianRelationship, GUARDIAN_RELATIONSHIP_COMPACT } from './context.js';18 import { isGuardianRelationship, GUARDIAN_RELATIONSHIP_COMPACT, carriesGuardians } from './context.js'; 19 19 import * as offers from './offers.js'; 20 20 import * as relations from './relations.js'; … … 139 139 const doc = await deps.fetchActor(candidateUri).catch(() => null); 140 140 if (!doc) return 'unverified'; 141 const g = doc['shaer:guardians']; 142 const has = Array.isArray(g) ? g.length > 0 143 : typeof g === 'string' ? g.length > 0 144 : (g && typeof g === 'object') ? (Array.isArray(g.items) ? g.items.length > 0 : true) 145 : false; 146 return has ? 'malformed' : 'ok'; 141 return carriesGuardians(doc) ? 'malformed' : 'ok'; 147 142 } 148 143
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)