source: Klonkt/test/follow-gating.test.js@ e62f65d

main
Last change on this file since e62f65d was 5c373b8, checked in by Robin <roboburr@…>, 7 weeks ago

Guardian 2: follow-goedkeuring (FEP-633c §5.3)

Een Follow op een ward wordt niet meer automatisch geaccepteerd: hij wacht op
goedkeuring van de guardians. Afgebakend zoals de spec: gating geldt ALLEEN voor
ward-actors (die guardians hebben); een gewone site zonder guardians accepteert
als vanouds, geen gedragswijziging. Een gecommitte guardian die zelf volgt wordt
wel meteen geaccepteerd (Barts regel: die heeft geen gate nodig, en zo werkt het
meekijken). Quorum per ward: 'any' (default, één volstaat), 'all' of 'none'; een
enkele reject weigert.

De guardian ziet de verzoeken in /guardian2 (ward en guardian zitten op dezelfde
familie-Klonkt, dus lokaal leesbaar) en tikt Accept/Deny. Bij goedkeuring stuurt
Klonkt de Accept(Follow) en legt de follower vast, zodat bezorging (ook
followers-only) begint. Cross-instance federatie van de goedkeuring is een latere
verfijning (de daemon heeft het patroon).

Changed files:
src/services/ActivityPubService.js

  • inbound Follow: gate voor ward-actors; acceptGatedFollow/rejectGatedFollow

src/config/database.js

  • ap_pending_follows + ap_pending_follow_approvals

src/services/guardianship/index.js

  • follows-module geexporteerd

src/routes/guardian2.js

  • GET /api/follow-requests, POST /api/follow/:id (guardian-gecheckt)

src/views/pages/guardian2.ejs, src/assets/js/guardian2.js

src/services/i18n.js

  • guardian2 follow_title/follow_sub (nl/en/de)

New file:
src/services/guardianship/follows.js

  • de gating-store + quorum-beslissing (any/all), pure en testbaar

test/follow-gating.test.js

  • any/all-quorum en single-reject

remarks: npm test 167/167. v1 /guardian en niet-ward-sites onaangeraakt.

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

  • Property mode set to 100644
File size: 1.5 KB
Line 
1// FEP-633c §5.3: follow-gating store. A follow on a ward waits for guardians.
2import { test } from 'node:test';
3import assert from 'node:assert/strict';
4
5process.env.DATABASE_PATH = ':memory:';
6process.env.PUBLIC_BASE_URL = 'https://test.example';
7
8const dbMod = await import('../src/config/database.js');
9dbMod.initializeDatabase();
10const G = await import('../src/services/guardianship/index.js');
11
12const A = (s) => `https://test.example/ap/users/${s}`;
13const [KID, MOM, DAD, STRANGER] = [A('kid'), A('mom'), A('dad'), A('stranger')];
14
15test('any-quorum: one guardian approval is enough', () => {
16 G.follows.recordPending('kid', { id: 'f1', follower: STRANGER, inbox: `${STRANGER}/inbox`, quorum: 'any' });
17 assert.equal(G.follows.listForWard('kid').length, 1);
18 const r = G.follows.decide('f1', MOM, 'approve', [MOM, DAD]);
19 assert.equal(r.outcome, 'approved');
20 G.follows.remove('f1');
21 assert.equal(G.follows.listForWard('kid').length, 0);
22});
23
24test('all-quorum: needs every guardian', () => {
25 G.follows.recordPending('kid', { id: 'f2', follower: STRANGER, inbox: `${STRANGER}/inbox`, quorum: 'all' });
26 assert.equal(G.follows.decide('f2', MOM, 'approve', [MOM, DAD]).outcome, 'waiting');
27 assert.equal(G.follows.decide('f2', DAD, 'approve', [MOM, DAD]).outcome, 'approved');
28});
29
30test('a single reject denies the follow (§3.2 spirit)', () => {
31 G.follows.recordPending('kid', { id: 'f3', follower: STRANGER, inbox: `${STRANGER}/inbox`, quorum: 'any' });
32 const r = G.follows.decide('f3', DAD, 'reject', [MOM, DAD]);
33 assert.equal(r.outcome, 'rejected');
34});
Note: See TracBrowser for help on using the repository browser.