Ignore:
Timestamp:
08/08/2026 02:33:28 AM (5 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
06bae1e
Parents:
50e80ac
Message:

De gate-familie functioneel: acht poorten die echt schakelen (shaer-ahy.1)

Barts opdracht (8-8): "maak ze allemaal maar functioneel." Alle
setting-gates uit de catalogus hebben nu een kolom, zijn voorstelbaar en
beslisbaar via de bestaande tally, en worden ECHT afgedwongen -- bij de
aflevering (wat dicht is wordt nooit geserialiseerd, de regel die de
embeds al hadden) of bij de inname (wat de ward niet mag versturen
weigert de outbox met een eerlijke 403).

externalThreads de thread-kring (shaer-9y2): dicht is de kring van de

guardians met telling, open is alles; per verzoek en
buiten de threadcache om, want een poort die net
dichtging mag niet twee minuten open nawerken

images/music bijlagen gefilterd op mediaType, ook in de thread
quoteCards shaer:quote niet geserialiseerd
customEmoji Emoji-tags en byline-emoji niet geserialiseerd; de

:shortcode: blijft als tekst staan, dat is eerlijk

messages de berichten-poot dicht voor vreemden en vrienden,

maar NOOIT voor het guardian-kanaal, en de outbox
weigert directe berichten -- behalve de reddingsboei:
een poort die het hulpkanaal afsnijdt beschermt
niemand

compose de outbox weigert eigen posts; een antwoord valt

onder het gesprek, niet onder een eigen podium

accountMove de harde weigering van shaer-tge is een gate

geworden met dezelfde standaard: guardians kunnen
hem nu openzetten

De capabilities dragen de hele familie, zodat de app VOORAF weet wat hij
mag aanbieden -- de (+) kaart leest shaer:compose al. De voorstelroute
herschreef een onbekende feature stilletjes naar externalEmbeds; dat is
nu een 400, want een voorstel dat op de verkeerde poort landt mag een
guardian nooit overkomen. Het paneel leest de standen catalogusbreed.

TWEE blijven bewust gepland. publicProfile is niet een veld dat je
wegfiltert maar het hele publieke web-oppervlak (de ontwerpvraag van
shaer-hj0); een half slot leest als een heel slot en dat is gevaarlijker
dan geen. independence draagt gezag over een kind over en zijn vorm
hoort bij shaer-90v beslist te worden, niet hier geimproviseerd.

Zestien nieuwe/aangepaste toetsen, alle 640 groen.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    r50e80ac r3b43e4c  
    29192919      case 'Create': {
    29202920        if (!object || typeof object !== 'object') return { status: 400, error: 'missing_object' };
     2921        // Innamepoorten (shaer-ahy.1, 8-8): wat de ward niet mag versturen
     2922        // wordt HIER geweigerd, niet in de app verstopt -- een knop die de
     2923        // client alleen verbergt is geen poort. De reddingsboei gaat ALTIJD
     2924        // voor: een hulpvraag aan de guardians mag door elke dichte deur heen,
     2925        // anders sluit een messages-poort precies het kanaal af dat het kind
     2926        // veilig houdt.
     2927        {
     2928          const isWard = (() => { try { return Guardianship.listGuardians(site.slug).length > 0; } catch { return false; } })();
     2929          const isHelp = object['shaer:helpRequest'] === true || object.helpRequest === true;
     2930          const direct = c2sVisibility(object) === 'direct';
     2931          if (!isHelp) {
     2932            if (direct && !Guardianship.wardGateAllowed(site.gate_messages, isWard)) {
     2933              return { status: 403, error: 'gated_messages' };
     2934            }
     2935            if (!direct && !object.inReplyTo && !Guardianship.wardGateAllowed(site.gate_compose, isWard)) {
     2936              return { status: 403, error: 'gated_compose' };
     2937            }
     2938          }
     2939        }
    29212940        // Client sends `source` (plain/markdown) + `content` (HTML). deliverReply
    29222941        // re-escapes, so it needs plain text; a top-level post keeps sanitized HTML.
     
    33853404 * De directe antwoorden op één note, genormaliseerd voor de C2S-lezer.
    33863405 *
    3387  * `circle` is de wachtwoord-vraag van shaer-vw4 in zijn veiligste stand: is de
    3388  * lezer een ward, dan komen alleen antwoorden door van accounts die de
    3389  * guardians al kennen (gevolgd of volgend). Wat er buiten valt wordt GETELD en
    3390  * als aantal teruggegeven, nooit stil weggelaten -- maar het besluit of dat
    3391  * aantal getoond wordt, en of dit de blijvende regel is, ligt bij Bart en
    3392  * Robin (shaer-vw4). Geblokkeerde actors zijn een andere categorie: die
    3393  * verdwijnen zonder telling, een blokkade is onzichtbaar.
     3406 * ALLEEN ophalen en normaliseren; de poorten zitten in de route. De
     3407 * kringfilter (de dichte stand van shaer:externalThreads) woont in
     3408 * filterThreadToCircle en de beeld/muziek/emoji-poorten in
     3409 * gateAttachments/stripEmojiTags -- per verzoek, buiten deze cache om, want de
     3410 * stand van een poort mag hier niet twee minuten bevriezen. Geblokkeerde
     3411 * actors zijn een andere categorie en verdwijnen WEL hier, zonder telling:
     3412 * een blokkade is onzichtbaar, ook als getal.
    33943413 */
    3395 export async function getThread(slug, objectUri, { isWard = false } = {}) {
     3414export async function getThread(slug, objectUri) {
    33963415  const key = `${slug}|${objectUri}`;
    33973416  const hit = threadViewCache.get(key);
     
    34343453  }));
    34353454
    3436   const circle = isWard ? (() => {
    3437     const s = new Set();
    3438     try { for (const r of db.prepare("SELECT actor_uri FROM ap_following WHERE slug = ? AND status = 'accepted'").all(slug)) s.add(r.actor_uri); } catch { /* geen tabel */ }
    3439     try { for (const r of db.prepare('SELECT actor_uri FROM ap_followers WHERE slug = ?').all(slug)) s.add(r.actor_uri); } catch { /* geen tabel */ }
    3440     return s;
    3441   })() : null;
    3442 
    3443   let hidden = 0;
    34443455  const kept = [];
    34453456  for (const o of objs) {
     
    34473458    const actorUri = actorUriOf(o.attributedTo);
    34483459    if (!actorUri || isBlockedAny(actorUri)) continue;   // een blokkade telt niet mee
    3449     if (circle && !circle.has(actorUri)) { hidden += 1; continue; }
    34503460    kept.push({ o, actorUri });
    34513461  }
     
    34893499  })).sort((a, b) => String(a.published || '').localeCompare(String(b.published || '')));
    34903500
    3491   const out = { notes, hidden, found: !!note };
     3501  const out = { notes, found: !!note };
    34923502  threadViewCache.set(key, { at: Date.now(), out });
    34933503  if (threadViewCache.size > THREAD_VIEW_CACHE_MAX) {
     
    34953505    if (oldest) threadViewCache.delete(oldest[0]);
    34963506  }
     3507  return out;
     3508}
     3509
     3510/**
     3511 * De thread gefilterd op de kring die de guardians al kennen (gevolgd of
     3512 * volgend) -- de dichte stand van shaer:externalThreads. PER VERZOEK, buiten de
     3513 * threadcache om: een poort die de guardians net dichtzetten mag niet nog twee
     3514 * minuten open nawerken uit een cache. Wat er buiten valt wordt GETELD, nooit
     3515 * stil weggelaten.
     3516 */
     3517export function filterThreadToCircle(slug, notes) {
     3518  const circle = new Set();
     3519  try { for (const r of db.prepare("SELECT actor_uri FROM ap_following WHERE slug = ? AND status = 'accepted'").all(slug)) circle.add(r.actor_uri); } catch { /* geen tabel */ }
     3520  try { for (const r of db.prepare('SELECT actor_uri FROM ap_followers WHERE slug = ?').all(slug)) circle.add(r.actor_uri); } catch { /* geen tabel */ }
     3521  const kept = [], out = { hidden: 0 };
     3522  for (const n of notes) {
     3523    if (circle.has(n.attributedTo)) kept.push(n);
     3524    else out.hidden += 1;
     3525  }
     3526  out.notes = kept;
    34973527  return out;
    34983528}
     
    39243954  return emojis.length ? JSON.stringify(emojis) : null;
    39253955}
     3956// ── Gate-filters voor de C2S-serialisatie (shaer-ahy.1, 8-8) ──────
     3957//
     3958// Dezelfde regel als bij de embeds: de poort zit bij de AFLEVERING. Een
     3959// bijlage die de client alleen verbergt is wel degelijk geleverd, dus wat
     3960// dicht is wordt hier nooit geserialiseerd. Puur, zodat de regels los van de
     3961// routes te toetsen zijn.
     3962
     3963/** Bijlagen door de beeld- en muziekpoort. Leeg wordt undefined, zoals de
     3964 *  serialisatie dat overal doet. */
     3965export function gateAttachments(atts, { images = true, audio = true } = {}) {
     3966  if (!Array.isArray(atts)) return atts;
     3967  const out = atts.filter((a) => {
     3968    const mt = String((a && a.mediaType) || '');
     3969    if (!images && mt.startsWith('image/')) return false;
     3970    if (!audio && (mt.startsWith('audio/') || (a && a.type === 'Audio'))) return false;
     3971    return true;
     3972  });
     3973  return out.length ? out : undefined;
     3974}
     3975
     3976/** Tag-array zonder de FEP-9098 Emoji-tags, voor een dichte emoji-poort. De
     3977 *  :shortcode: blijft als tekst staan -- dat is eerlijk: er STAAT iets, het
     3978 *  wordt alleen niet als plaatje van een vreemde server gerenderd. */
     3979export function stripEmojiTags(tags) {
     3980  if (!Array.isArray(tags)) return tags;
     3981  const out = tags.filter((t) => (Array.isArray(t && t.type) ? t.type[0] : (t && t.type)) !== 'Emoji');
     3982  return out.length ? out : undefined;
     3983}
     3984
    39263985export function timelineEmojis(emojiJson) {
    39273986  try { const arr = emojiJson ? JSON.parse(emojiJson) : null; return (Array.isArray(arr) && arr.length) ? arr : undefined; }
     
    49765035  if (!base || !site || !site.slug) return { error: 'config' };
    49775036  try {
    4978     const guardians = Guardianship.listGuardians(site.slug);
    4979     if (guardians.length) {
    4980       console.warn('[AP] move refused: guarded account (shaer-tge):', site.slug, '→', String(targetRaw || ''));
     5037    // Was een harde weigering voor elk bewaakt account (shaer-tge); sinds 8-8
     5038    // een GATE met dezelfde standaard: de automatiek weigert voor een ward,
     5039    // maar de guardians kunnen shaer:accountMove expliciet openzetten -- en
     5040    // expliciet dichtzetten geldt dan ook voor een account dat net geen ward
     5041    // meer is, net als bij de embeds.
     5042    const isWard = Guardianship.listGuardians(site.slug).length > 0;
     5043    if (!Guardianship.wardGateAllowed(site.gate_account_move, isWard)) {
     5044      console.warn('[AP] move refused: gated (shaer-tge):', site.slug, '→', String(targetRaw || ''));
    49815045      return { error: 'guarded_account' };
    49825046    }
     
    57005764  getNotifications, listBlocks, isBlockedAny, blockTarget, unblock,
    57015765  deliverWithRetry, enqueueDelivery, processDeliveryQueue, startDeliveryWorker,
    5702   getReplyUris, getThread, markNotificationsSeen, countUnseenNotifications, hasPlayableAudio,
     5766  getReplyUris, getThread, filterThreadToCircle, gateAttachments, stripEmojiTags, markNotificationsSeen, countUnseenNotifications, hasPlayableAudio,
    57035767  linkifyBody, bakePostContent, bakePostContentWithMentions, listFollowers, removeFollower, listConnections,
    57045768  noteVisibility, belongsInTimeline, playerUrlFor, isRejectedObject, rejectInteraction, interactionReportTarget,
Note: See TracChangeset for help on using the changeset viewer.