source: Klonkt/src/services/guardianship/relations.js@ 7e53594

main
Last change on this file since 7e53594 was 7e53594, checked in by Bart <bart@…>, 4 weeks ago

shaer:log: het logboek staat naast de wachtrijen, niet erin

De apps konden er niet bij. Het logboek zat achter /guardian/api/events, een
PWA-route, dus een ward kon niet lezen waarom haar eigen aanbod geweigerd was --
en dat is precies de helft die §4.2 eist.

Eigen sleutel op de actor, GET /ap/users/:slug/log, dezelfde eigenaar-only
bearer als de wachtrijen. Bewust niet onder shaer:queues: alles daar wacht op
een antwoord en dit is wat er al besloten is. Geschiedenis onderbrengen bij een
woord dat "wachtend" betekent maakt van twee dingen één, en dat is de fout die
deze module vandaag drie keer heeft opgeruimd.

type is 'shaer:Event' en 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.

Clients vinden hem via het actor-document, niet via een pad dat ze zelf
verzinnen (shaer-qa0). De AS2-test viel netjes om tot shaer:log was aangemeld --
tweede keer vandaag dat die het vangt.

De daemon volgt (shaer-6d9): zolang die geen shaer:log serveert, liegt een
client die tegen beide praat over een van de twee.

Co-Authored-By: Claude Opus 5 <claude@…>

  • Property mode set to 100644
File size: 4.7 KB
Line 
1/**
2 * Guardianship (FEP-633c) — the COMMITTED ward ↔ guardian relations
3 * (ap_guardianships). Pending offers live in offers.js; a row here means the
4 * handshake committed (§3.1.4). Every row is one relation seen from a LOCAL
5 * site: role 'guardian' = the site guards other_uri; role 'ward' = other_uri
6 * guards the site.
7 */
8import db from '../../config/database.js';
9
10let _s = null;
11function stmts() {
12 if (!_s) {
13 _s = {
14 commit: db.prepare(`INSERT INTO ap_guardianships (slug, role, other_uri, other_handle, status, offer_id, created_at)
15 VALUES (?,?,?,?, 'accepted', ?, CURRENT_TIMESTAMP)
16 ON CONFLICT(slug, role, other_uri) DO UPDATE SET status='accepted', offer_id=excluded.offer_id`),
17 del: db.prepare('DELETE FROM ap_guardianships WHERE slug=? AND role=? AND other_uri=?'),
18 bySlugRole: db.prepare("SELECT * FROM ap_guardianships WHERE slug=? AND role=? AND status='accepted' ORDER BY created_at DESC"),
19 one: db.prepare('SELECT * FROM ap_guardianships WHERE slug=? AND role=? AND other_uri=?'),
20 };
21 }
22 return _s;
23}
24
25// ── Reads ────────────────────────────────────────────────────────────────
26
27/** Accepted guardian URIs of a local ward (feeds shaer:guardians). */
28export function listGuardians(slug) { return stmts().bySlugRole.all(slug, 'ward'); }
29
30/** Accepted wards of a local guardian (the wards queue). */
31export function listWards(slug) { return stmts().bySlugRole.all(slug, 'guardian'); }
32
33/** A site is a guardian once it stands in any accepted guardian relation. */
34export function isGuardian(slug) { return listWards(slug).length > 0; }
35
36export function getRelation(slug, role, otherUri) { return stmts().one.get(slug, role, otherUri); }
37
38// ── Writes (only the handshake commit lands here) ────────────────────────
39
40/** The local ward gains a guardian (commit, §3.1.4). */
41export function commitGuardianForWard(wardSlug, guardianUri, { handle = null, offerId = null } = {}) {
42 stmts().commit.run(wardSlug, 'ward', guardianUri, handle, offerId);
43 return stmts().one.get(wardSlug, 'ward', guardianUri);
44}
45
46/** The local guardian gains a ward (commit, §3.1.4). */
47export function commitWardForGuardian(guardianSlug, wardUri, { handle = null, offerId = null } = {}) {
48 stmts().commit.run(guardianSlug, 'guardian', wardUri, handle, offerId);
49 return stmts().one.get(guardianSlug, 'guardian', wardUri);
50}
51
52/** End a relation locally (Undo, §3.2 — federation of the Undo is Fase 4). */
53export function removeRelation(slug, role, otherUri) {
54 stmts().del.run(slug, role, otherUri);
55 return { ok: true };
56}
57
58// ── Actor document (FEP-633c §2) ─────────────────────────────────────────
59
60/**
61 * Guardianship props for a local actor doc. `id` is the actor URI.
62 * - shaer:guardians: accepted guardians of this ward (omitted when none, §2.1)
63 * - shaer:isGuardian: true once the site guards anyone
64 * - shaer:queues: the owner-only dashboard collections
65 *
66 * §1 mutual exclusion: a ward (has guardians) is never a guardian, so
67 * shaer:isGuardian is suppressed if guardians exist; the offer path already
68 * bars a ward from offering.
69 */
70export function actorProps(id, slug) {
71 const props = {
72 'shaer:queues': {
73 offers: `${id}/queues/offers`,
74 follows: `${id}/queues/follows`,
75 // Both directions of §5.3, kept apart on purpose: a guardian must be able
76 // to tell "someone wants to follow your ward" from "your ward wants to
77 // follow someone". Same mechanism, opposite question, different words in
78 // the interface (shaer-p729).
79 outgoingFollows: `${id}/queues/outgoing-follows`,
80 wards: `${id}/queues/wards`,
81 guardians: `${id}/queues/guardians`,
82 help: `${id}/queues/help`,
83 },
84 // NAAST de wachtrijen, niet erin. Alles onder shaer:queues wacht op een
85 // antwoord; dit is wat er al besloten is, met de reden erbij (§4.2).
86 // Geschiedenis onderbrengen bij een woord dat "wachtend" betekent maakt van
87 // twee dingen één, en dat is precies de fout die de rest van deze module
88 // net heeft opgeruimd.
89 'shaer:log': `${id}/log`,
90 };
91 const guardians = listGuardians(slug).map((r) => r.other_uri);
92 if (guardians.length) {
93 props['shaer:guardians'] = guardians; // a ward
94 } else if (isGuardian(slug)) {
95 props['shaer:isGuardian'] = true; // a guardian (never both, §1)
96 }
97 return props;
98}
99
100export default {
101 listGuardians, listWards, isGuardian, getRelation,
102 commitGuardianForWard, commitWardForGuardian, removeRelation, actorProps,
103};
Note: See TracBrowser for help on using the repository browser.