Ignore:
Timestamp:
10/12/2026 04:15:08 PM (2 days ago)
Author:
Bart <bart@…>
Branches:
main
Children:
61d52ed
Parents:
746d98a
Message:

Zachte groepen: een gesprek is de set deelnemers, niet één tegenpartij

Een direct bericht aan Anna en Ben bestond al als groep -- to_actors droeg de
hele set -- maar elke lezing sleutelde op één other. Een uitgaand bericht
aan twee mensen viel in twee gesprekken uiteen, en een binnenkomend bericht
van Anna aan jou en Ben kwam alleen bij Anna terecht: Ben viel eruit, want de
adressering van een binnenkomer werd nergens bewaard.

Nu is er een sleutel, één keer berekend bij het schrijven
(conversation-key.js): voor één ander zijn kale URI, precies zoals het altijd
was, dus bestaande gesprekken, leesmarkeringen en ?with=-URL's blijven geldig
zonder migratie; voor een groep de gesorteerde set, gescheiden door een
spatie. Koppen, geschiedenis, leesmarkeringen en ongelezen lezen hem allemaal
via dezelfde unie, en werken daarmee per gesprek zonder dat er in die queries
iets hoefde te veranderen. Oude rijen hebben geen sleutel en lezen zoals ze
altijd lazen.

Alleen directe berichten groeperen: een openbare post aan jou en Ben is geen
besloten gesprek met z'n drieën.

/messages neemt meer dan één with, en elk item draagt shaer:conversation en
shaer:participants. Uitgaande directe notes krijgen een FEP-7888 context --
die van de ouder als het een antwoord is, anders een nieuwe -- die bewaard en
uitgestuurd wordt, maar geen bubbels samenvoegt: een bubbel die Carla toont
terwijl je antwoord haar niet bereikt, zou liegen over wie meeleest.

shaer-mbji
Co-Authored-By: Claude Opus 5.5 <claude@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/guardianship/delivery.js

    r746d98a r6e9bc70  
    1414import db, { NU_ISO } from '../../config/database.js';
    1515import { carriesGuardians } from './context.js';
     16import * as ConversationKey from '../conversation-key.js';
    1617
    1718const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
    … …  
    3334  if (!to.length && !cc.length) return 'public';   // no addressing at all: legacy client, keep old behavior
    3435  return 'direct';
     36}
     37
     38/**
     39 * De context (FEP-7888) van het bericht waarop geantwoord wordt, als we die
     40 * kennen: uit wat binnenkwam of uit wat we zelf stuurden. Een antwoord hoort in
     41 * het gesprek van zijn ouder, en niet in een vers gesprek ernaast.
     42 */
     43function contextFor(slug, inReplyTo) {
     44  if (!inReplyTo) return null;
     45  try {
     46    const binnen = db.prepare('SELECT context FROM ap_mentions WHERE slug = ? AND object_uri = ? AND context IS NOT NULL')
     47      .get(slug, inReplyTo);
     48    if (binnen && binnen.context) return binnen.context;
     49    // Een eigen note heeft als uri .../ap/notes/<id>; de id is de outbox-sleutel.
     50    const eigen = /\/ap\/notes\/([^/?#]+)$/.exec(String(inReplyTo));
     51    if (eigen) {
     52      const rij = db.prepare('SELECT context FROM ap_outbox WHERE site_slug = ? AND id = ? AND context IS NOT NULL')
     53        .get(slug, eigen[1]);
     54      if (rij && rij.context) return rij.context;
     55    }
     56  } catch { /* geen context is ook een antwoord */ }
     57  return null;
    3558}
    3659
    … …  
    144167    .map((a) => ({ url: a.url, mediaType: String(a.mediaType), name: String(a.name || '').slice(0, 120) }));
    145168  const id = crypto.randomUUID();
    146   db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, wave, away_until, created_at)
    147               VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`)
    148     .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0, wave ? 1 : 0, awayUntil || null);
     169  // Het gesprek waar dit bij hoort (shaer-mbji): de set ontvangers. Aan Anna en
     170  // Ben samen is één gesprek, en niet twee die elk de helft dragen.
     171  const conv = ConversationKey.conversationKey(resolved.map((r) => r.uri));
     172  // FEP-7888: het gesprek een naam geven die andere servers ook lezen. Is dit
     173  // een antwoord op iets met een context, dan hoort het in diezelfde; anders
     174  // begint hier een nieuwe.
     175  const context = contextFor(site.slug, inReplyTo) || `${base}/ap/contexts/${crypto.randomUUID()}`;
     176  db.prepare(`INSERT INTO ap_outbox (id, site_slug, post_id, post_slug, in_reply_to, to_actor, to_handle, content, language, attachments, visibility, to_actors, help_request, wave, away_until, conv, context, created_at)
     177              VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,${NU_ISO})`)
     178    .run(id, site.slug, '', null, inReplyTo || null, resolved[0].uri, resolved[0].handle, content, lang, media.length ? JSON.stringify(media) : null, 'direct', JSON.stringify(resolved.map((r) => r.uri)), helpRequest ? 1 : 0, wave ? 1 : 0, awayUntil || null, conv, context);
    149179  const row = getOutboxRow(id);
    150180  const note = buildReplyNote(base, site, row);
Note: See TracChangeset for help on using the changeset viewer.