source: Klonkt/src/services/guardianship/relations.js@ c26cc18

main
Last change on this file since c26cc18 was 6b5d7da, checked in by Robin Genis <roboburr@…>, 7 weeks ago

Guardianship als eigen module + gedeelde BlocklistService

FEP-633c (Guardians) lag verspreid door ActivityPubService; nu is het één
cohesief onderdeel in src/services/guardianship/ met submodules. De
blocklist staat er bewust NAAST (BlocklistService): die wordt gedeeld met
Klonkt zelf (Block-tab) en is niet guardianship-specifiek.

De module importeert ActivityPubService nooit terug: de AP-helpers gaan er
één keer in via wireDelivery/wireHandshake, en de service delegeert met
dunne wrappers zodat elke bestaande aanroep blijft werken.

Naast de verhuizing ook de serverkant die nog miste (shaer-bh1): de
ap_guardianships-relaties, shaer:guardians/isGuardian/queues op het
actor-doc, en de adoptie-handshake Offer/Accept/Reject over C2S en S2S,
met het contract van de Shaer test-daemon zodat de iOS/Android-clients het
ongewijzigd spreken. Een inkomend hulpverzoek (shaer:helpRequest op een
directe mention) krijgt een eigen vlag in ap_mentions en pusht als
'help'-type richting de Guardian-PWA (volgende commit).

Changed files:
src/services/ActivityPubService.js

  • shaer-context, actor-props en helpRequest uit de module gespread
  • blocklist-functies zijn delegaties naar BlocklistService
  • c2sVisibility/deliverDirectNote re-export uit guardianship/delivery
  • C2S: Offer/Accept/Reject eerst langs de handshake-module
  • S2S: Offer aan GATED (signature-eis) + handshake-routering
  • inbound mention: help_request-vlag + 'help'/'guardian'-push-events

src/config/database.js

  • tabel ap_guardianships (slug, role, other_uri, status, offer_id)
  • kolom ap_mentions.help_request

test/activitypub-as2.test.js

  • shaer:queues/offers/follows/wards in de AS2-allowlist

New file:
src/services/BlocklistService.js

  • ap_blocks-opslag, blockTarget/unblock/listBlocks/isBlockedAny, purge; handle-resolver via injectie (geen circulaire import)

src/services/guardianship/index.js

  • de publieke API van het onderdeel

src/services/guardianship/context.js

  • shaer-namespace + Relationship-vocabulaire

src/services/guardianship/relations.js

  • ap_guardianships-API + actor-props (FEP-633c paragraaf 2)

src/services/guardianship/handshake.js

src/services/guardianship/queues.js

src/services/guardianship/notes.js

  • shaer:helpRequest lezen/schrijven

src/services/guardianship/delivery.js

  • de directe-note-route (call-for-help), gedrag ongewijzigd

remarks: alle 158 tests groen. push-teksten (push.n_help_*, push.n_guard_*)
en de queue-routes + Guardian-PWA volgen in de volgende commits.

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

  • Property mode set to 100644
File size: 4.5 KB
Line 
1/**
2 * Guardianship (FEP-633c) — the ward ↔ guardian relations (ap_guardianships).
3 *
4 * Every row is one relation seen from a LOCAL site: role 'guardian' means the
5 * site guards `other_uri` (a ward, possibly remote); role 'ward' means
6 * `other_uri` guards the site. A local ward with a local guardian yields two
7 * rows, one per perspective — intentional, each side reads its own.
8 *
9 * The handshake (spec §3): the guardian-candidate — and only the candidate —
10 * Offers a Relationship {subject: ward, relationship: shaer:Guardian,
11 * object: candidate}; the ward Accepts (or Rejects). Status walks
12 * 'offered' → 'accepted'; a Reject deletes the row.
13 */
14import db from '../../config/database.js';
15
16let _s = null;
17function stmts() {
18 if (!_s) {
19 _s = {
20 ins: db.prepare(`INSERT OR IGNORE INTO ap_guardianships (slug, role, other_uri, other_handle, status, offer_id, created_at)
21 VALUES (?,?,?,?,?,?,CURRENT_TIMESTAMP)`),
22 accept: db.prepare(`UPDATE ap_guardianships SET status='accepted' WHERE slug=? AND role=? AND other_uri=?`),
23 del: db.prepare('DELETE FROM ap_guardianships WHERE slug=? AND role=? AND other_uri=?'),
24 bySlugRole: db.prepare('SELECT * FROM ap_guardianships WHERE slug=? AND role=? ORDER BY created_at DESC'),
25 one: db.prepare('SELECT * FROM ap_guardianships WHERE slug=? AND role=? AND other_uri=?'),
26 byOffer: db.prepare('SELECT * FROM ap_guardianships WHERE offer_id=?'),
27 };
28 }
29 return _s;
30}
31
32// ── Reads ────────────────────────────────────────────────────────────────
33
34/** Accepted guardian URIs of a local ward (feeds shaer:guardians). */
35export function listGuardians(slug) {
36 return stmts().bySlugRole.all(slug, 'ward').filter((r) => r.status === 'accepted');
37}
38
39/** All ward relations of a local guardian (accepted + pending offers). */
40export function listWards(slug) {
41 return stmts().bySlugRole.all(slug, 'guardian');
42}
43
44/** Pending offers where the local site is a party (either side). */
45export function listOffers(slug) {
46 return [...stmts().bySlugRole.all(slug, 'guardian'), ...stmts().bySlugRole.all(slug, 'ward')]
47 .filter((r) => r.status === 'offered');
48}
49
50/** A site is a guardian once it stands in any guardian-side relation. */
51export function isGuardian(slug) {
52 return stmts().bySlugRole.all(slug, 'guardian').length > 0;
53}
54
55export function getRelation(slug, role, otherUri) { return stmts().one.get(slug, role, otherUri); }
56export function findByOfferId(offerId) { return offerId ? stmts().byOffer.all(offerId) : []; }
57
58// ── Writes (the handshake walks through these) ───────────────────────────
59
60/** Record an outgoing/incoming Offer on the local side with `role`. */
61export function recordOffer(slug, role, otherUri, { handle = null, offerId = null } = {}) {
62 stmts().ins.run(slug, role, otherUri, handle, 'offered', offerId);
63 return stmts().one.get(slug, role, otherUri);
64}
65
66/** The ward said yes (or our own offer was accepted): relation becomes real. */
67export function acceptRelation(slug, role, otherUri) {
68 stmts().accept.run(slug, role, otherUri);
69 return stmts().one.get(slug, role, otherUri);
70}
71
72/** Reject / retract / end a relation: the row disappears. */
73export function removeRelation(slug, role, otherUri) {
74 stmts().del.run(slug, role, otherUri);
75 return { ok: true };
76}
77
78// ── Actor document (FEP-633c §2) ─────────────────────────────────────────
79
80/**
81 * The guardianship properties for a local actor doc. `id` is the actor URI.
82 * - shaer:guardians: accepted guardians of this ward (omitted when none)
83 * - shaer:isGuardian: true once the site guards anyone
84 * - shaer:queues: the owner-only dashboard collections (always advertised,
85 * like `blocked`: clients discover, the routes enforce auth)
86 */
87export function actorProps(id, slug) {
88 const props = {
89 'shaer:queues': {
90 offers: `${id}/queues/offers`,
91 follows: `${id}/queues/follows`,
92 wards: `${id}/queues/wards`,
93 },
94 };
95 const guardians = listGuardians(slug).map((r) => r.other_uri);
96 if (guardians.length) props['shaer:guardians'] = guardians;
97 if (isGuardian(slug)) props['shaer:isGuardian'] = true;
98 return props;
99}
100
101export default {
102 listGuardians, listWards, listOffers, isGuardian, getRelation, findByOfferId,
103 recordOffer, acceptRelation, removeRelation, actorProps,
104};
Note: See TracBrowser for help on using the repository browser.