source: Klonkt/src/services/guardianship/help.js@ 3e7dde2

main
Last change on this file since 3e7dde2 was 3e7dde2, checked in by Robin <roboburr@…>, 2 weeks ago

Een markering op een hulpvraag is een antwoord, dus draagt hij inReplyTo

Barts vraag (26-8): in Berichten staat "Ik kijk hiernaar." maar een tik erop
geeft niets -- welke context heeft dat bericht?

Geen, bleek. De verwijzing bestond wel (shaer:helpPickup draagt de uri van de
hulpvraag, het guardian-dashboard leeft ervan), maar hij overleefde de reis
naar het berichtenscherm niet: ap_mentions heeft er geen kolom voor, dus de
berichtenlezing serveert hem niet. En de tik-route van de clients volgt
inReplyTo -- die de markering niet droeg.

De reparatie is een regel, op de plek die alle wegen delen: deliverDirectNote
vult inReplyTo met de hulpvraag-uri zodra er een helpMark meereist en de
aanroeper niets anders zegt. Semantisch klopt het gewoon -- de markering gaat
ergens over, en inReplyTo is hoe je dat zegt zonder dialect. Een tik opent nu
de draad met de schermafdruk erbij, en andere fediverse-servers threaden hem
net zo goed. De clients hoeven niets.

markerNote (de canonieke vorm in help.js) zegt het ook, zodat de twee niet
uiteenlopen. Een expliciet meegegeven inReplyTo wint; de markering vult alleen
het gat. Toetsen op het gedeelde punt, over de loopback, zonder netwerk.

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

  • Property mode set to 100644
File size: 7.1 KB
Line 
1/**
2 * Wie er op een hulpvraag af is, en wanneer hij is afgesloten (shaer-lgo).
3 *
4 * Een hulpvraag (FEP-633c 5.2.1) gaat naar ALLE guardians van een kind, die op
5 * verschillende servers zitten. Zonder gedeelde staat denken er twee dat de
6 * ander het oppakt -- en dat is precies het scenario waar de reddingsboei voor
7 * bestaat.
8 *
9 * DE FAALSTAND IS HIER NIET VEILIG, en dat maakt dit anders dan elke gate. Bij
10 * een gate is "dicht" het veilige antwoord. Hier is de faalstand "iedereen denkt
11 * dat het geregeld is", en dat is gevaarlijker dan geen markering. Daaruit volgt
12 * de regel die overal in dit bestand terugkomt: bij twijfel is een hulpvraag
13 * OPEN.
14 *
15 * Twee besluiten van Bart (7-8) zitten in de vorm:
16 *
17 * OPGEPIKT mag stapelen en vervalt niet, maar VEROUDERT zichtbaar. Twee mensen
18 * die tegelijk reageren op een kind is geen probleem; twee die allebei niets
19 * doen omdat de ander het "geclaimd" had, wel. En een signaal dat vanzelf
20 * verdwijnt laat een hulpvraag er onaangeroerd uitzien terwijl er iemand mee
21 * bezig is.
22 *
23 * AFGEHANDELD kent geen terugdraai. Sluiten gebeurt met een stevige
24 * bevestiging, en leeft de vraag daarna nog, dan wordt hij OPNIEUW GESTELD --
25 * een nieuwe hulpvraag. Er wordt niets herschreven, er wordt toegevoegd.
26 */
27
28import db from '../../config/database.js';
29
30let _s = null;
31function stmts() {
32 if (!_s) {
33 _s = {
34 ins: db.prepare(`INSERT OR IGNORE INTO ap_help_state (note_uri, guardian_uri, kind, guardian_handle)
35 VALUES (?,?,?,?)`),
36 forNote: db.prepare('SELECT * FROM ap_help_state WHERE note_uri = ? ORDER BY created_at ASC'),
37 forNotes: db.prepare('SELECT * FROM ap_help_state WHERE note_uri IN (SELECT value FROM json_each(?))'),
38 };
39 }
40 return _s;
41}
42
43/** Leg vast dat iemand deze hulpvraag heeft opgepikt of afgesloten. */
44export function record(noteUri, guardianUri, kind, handle = null) {
45 if (!noteUri || !guardianUri) return;
46 const k = kind === 'handled' ? 'handled' : 'pickup';
47 try { stmts().ins.run(noteUri, guardianUri, k, handle); } catch { /* nooit dragend */ }
48}
49
50/**
51 * De staat van een hulpvraag, uit zijn rijen. Puur, zodat de regels te toetsen
52 * zijn zonder database of scherm.
53 *
54 * `oldestPickupAt` is het TIJDSTIP van de oudste oppik, niet de leeftijd. Daar
55 * tekent het scherm mee dat een signaal oud wordt -- niets verdwijnt, maar je
56 * ziet wel dat er misschien niets meer gebeurt.
57 *
58 * EEN TIJDSTIP, GEEN LEEFTIJD, en dat is geen smaak. Hier stond `ageMs`, een
59 * verschil met `now`, en dus veranderde dit antwoord elke milliseconde. Zodra
60 * het paneel een ETag kreeg (9-8) kon die daardoor nooit meer gelijk zijn: de
61 * 304 kwam nooit, de lange poll keerde meteen terug, en de browser kwam in een
62 * lus van ongeveer een seconde waarin de scrollpositie werd vermalen. Een
63 * levende klok in een antwoord maakt dat antwoord onvergelijkbaar met zichzelf.
64 *
65 * De leeftijd is een weergavedetail en wordt in de client uitgerekend.
66 */
67export function helpStatus(rows, now = Date.now()) {
68 const list = rows || [];
69 const pickups = list.filter((r) => r.kind === 'pickup');
70 const done = list.find((r) => r.kind === 'handled') || null;
71 const stamp = (r) => { const t = Date.parse(r.created_at); return isNaN(t) ? null : t; };
72 const oudste = pickups.map(stamp).filter((t) => t !== null).sort((a, b) => a - b)[0];
73 return {
74 // Namen erbij: "door wie" was de hele vraag. Zonder dat is het een vinkje.
75 pickedUpBy: pickups.map((r) => ({ uri: r.guardian_uri, handle: r.guardian_handle || null, at: r.created_at })),
76 handled: done ? { uri: done.guardian_uri, handle: done.guardian_handle || null, at: done.created_at } : null,
77 // Alleen betekenisvol zolang er niets is afgesloten.
78 oldestPickupAt: (!done && oudste) ? new Date(oudste).toISOString() : null,
79 // Waar het scherm op afgaat. Bij twijfel OPEN: een lege lijst, een rij die we
80 // niet kunnen lezen, wat dan ook -- alles wat geen expliciete afsluiting is,
81 // is een hulpvraag die nog op iemand wacht.
82 open: !done,
83 };
84}
85
86/**
87 * Een hulpvraag van iemand die je NIET MEER bewaakt.
88 *
89 * Het loslaat-scherm belooft dit al letterlijk: "je krijgt geen hulpvragen meer
90 * van ze". Nieuwe komen inderdaad niet meer binnen, maar wat er al lag bleef in
91 * de open lijst staan -- en was niet af te sluiten, want de markeerroute eist
92 * dat het nog je ward is en antwoordt anders met 403. De knop stond er dus wel
93 * en deed niets.
94 *
95 * Zo'n vraag is niet AFGEHANDELD -- dat zou een claim zijn over een kind waar je
96 * niets meer over te zeggen hebt, en die claim wordt ook nog rondgestuurd. Hij is
97 * niet meer van jou. Dat is een derde uitkomst en die hoort als zodanig te lezen.
98 *
99 * Veilig omdat een guardianship nooit bij de LAATSTE guardian eindigt (3.4,
100 * emancipatie): er blijft altijd iemand over voor wie de vraag wel open staat.
101 */
102export function withWardship(status, stillWard) {
103 if (stillWard) return status;
104 return { ...status, open: false, formerWard: true };
105}
106
107/** De staat van een hulpvraag zoals die nu is opgeslagen. */
108export function statusOf(noteUri, now = Date.now()) {
109 if (!noteUri) return helpStatus([], now);
110 try { return helpStatus(stmts().forNote.all(noteUri), now); } catch { return helpStatus([], now); }
111}
112
113/** Idem voor een hele lijst in een query, zodat een paneel geen N+1 wordt. */
114export function statusFor(noteUris, now = Date.now()) {
115 const uit = new Map();
116 const lijst = [...new Set((noteUris || []).filter(Boolean))];
117 if (!lijst.length) return uit;
118 let rijen = [];
119 try { rijen = stmts().forNotes.all(JSON.stringify(lijst)); } catch { rijen = []; }
120 const perNote = new Map();
121 for (const r of rijen) {
122 if (!perNote.has(r.note_uri)) perNote.set(r.note_uri, []);
123 perNote.get(r.note_uri).push(r);
124 }
125 for (const uri of lijst) uit.set(uri, helpStatus(perNote.get(uri) || [], now));
126 return uit;
127}
128
129/**
130 * De markering als bericht. Bewust een gewone directe note met een
131 * shaer:-markering, zoals de zwaai en de afwezigheidsmelding: dan reist het over
132 * de bestaande bezorging, en de WARD leest het als wat het is -- er komt iemand.
133 */
134export function markerNote({ id, me, noteUri, kind, to }) {
135 const k = kind === 'handled' ? 'handled' : 'pickup';
136 return {
137 id, type: 'Note', attributedTo: me, to,
138 // Ook als antwoord, in gewoon AS2 (26-8): de markering gaat ergens over,
139 // en inReplyTo is hoe je dat zegt zonder dialect. Zie deliverDirectNote,
140 // die dit voor de echte bezorging net zo doet.
141 inReplyTo: noteUri,
142 [k === 'handled' ? 'shaer:helpHandled' : 'shaer:helpPickup']: noteUri,
143 content: k === 'handled'
144 ? '<p>Deze hulpvraag is afgehandeld.</p>'
145 : '<p>Ik kijk hiernaar.</p>',
146 };
147}
148
149/** Leest een binnengekomen note als markering, of null als hij er geen is. */
150export function parseMarker(object) {
151 if (!object || typeof object !== 'object') return null;
152 const pickup = object['shaer:helpPickup'];
153 const handled = object['shaer:helpHandled'];
154 if (typeof handled === 'string' && handled) return { kind: 'handled', noteUri: handled };
155 if (typeof pickup === 'string' && pickup) return { kind: 'pickup', noteUri: pickup };
156 return null;
157}
158
159export default { record, helpStatus, withWardship, statusOf, statusFor, markerNote, parseMarker };
Note: See TracBrowser for help on using the repository browser.