Changeset 3b43e4c in Klonkt for src/routes/activitypub.js


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/routes/activitypub.js

    r50e80ac r3b43e4c  
    317317  const playbackAllowed = embedsAllowed
    318318    && Guardianship.externalPlaybackAllowed(auth.site.external_playback, isWard);
     319  // De rest van de familie (shaer-ahy.1, 8-8): zelfde regel, zelfde plek --
     320  // de poort zit bij de serialisatie, wat dicht is wordt nooit geleverd.
     321  const gate = (col) => Guardianship.wardGateAllowed(auth.site[col], isWard);
     322  const imagesAllowed = gate('gate_images');
     323  const musicAllowed = gate('gate_music');
     324  const quotesAllowed = gate('gate_quote_cards');
     325  const emojiAllowed = gate('gate_custom_emoji');
     326  const messagesAllowed = gate('gate_messages');
     327  const composeAllowed = gate('gate_compose');
     328  const threadsAllowed = gate('external_threads');
     329  // Emoji dicht raakt ook de bylines: de plaatjes in een naam komen net zo
     330  // goed van een vreemde server. De naam zelf blijft, met :shortcode: als tekst.
     331  const gateAuthor = (a) => (a && !emojiAllowed ? { ...a, emojis: undefined } : a);
    319332  const rows = AP.getTimeline(auth.site.slug, 60);
    320333  // Eén query voor de hele pagina (shaer-9e9 fase 2): shaer:liked komt uit de
     
    338351      // Friends' media travels along (media_json → AS2 attachment), so the
    339352      // client renders their images/audio like own outbox posts.
    340       attachment: AP.timelineAttachments(t.media_json),
     353      attachment: AP.gateAttachments(AP.timelineAttachments(t.media_json), { images: imagesAllowed, audio: musicAllowed }),
    341354      // The note's preserved tags, so the client can render them: FEP-9098
    342355      // Emoji tags (:shortcode: → image) and FEP-e232 Link tags (quotes /
     
    344357      // when the note has neither.
    345358      tag: (() => {
    346         const tags = [...(AP.timelineEmojis(t.emoji_json) || []), ...(AP.timelineObjectLinks(t.link_json) || [])];
     359        const tags = [...(emojiAllowed ? (AP.timelineEmojis(t.emoji_json) || []) : []), ...(AP.timelineObjectLinks(t.link_json) || [])];
    347360        return tags.length ? tags : undefined;
    348361      })(),
     
    350363      // renders an embedded quote card instead of a bare link. Omitted when the
    351364      // note has no quote or the quoted post could not be resolved.
    352       'shaer:quote': AP.timelineQuote(t.quote_json),
     365      'shaer:quote': quotesAllowed ? AP.timelineQuote(t.quote_json) : undefined,
    353366      // The post author's display info (name / @handle / avatar), so every card
    354367      // gets a byline header like the quote card. attributedTo stays the bare
    355368      // actor URI; this is the resolved presentation Klonkt already stored.
    356       'shaer:author': (t.author_name || t.author_handle || t.author_icon) ? {
     369      'shaer:author': gateAuthor((t.author_name || t.author_handle || t.author_icon) ? {
    357370        name: t.author_name || undefined, handle: t.author_handle || undefined,
    358371        icon: t.author_icon || undefined, url: t.author_url || undefined,
    359372        // FEP-9098: emojis in the display name (":shortcode:"), if any.
    360373        emojis: (() => { try { return t.author_emoji_json ? JSON.parse(t.author_emoji_json) : undefined; } catch { return undefined; } })(),
    361       } : undefined,
     374      } : undefined),
    362375      // When a followed account boosted this, who did ("X boosted"). Omitted for
    363376      // ordinary posts.
    364       'shaer:booster': (t.reblog_name || t.reblog_handle || t.reblog_icon) ? {
     377      'shaer:booster': gateAuthor((t.reblog_name || t.reblog_handle || t.reblog_icon) ? {
    365378        name: t.reblog_name || undefined, handle: t.reblog_handle || undefined,
    366379        icon: t.reblog_icon || undefined,
    367380        // FEP-9098: emojis in the booster's display name (":shortcode:"), if any.
    368381        emojis: (() => { try { return t.reblog_emoji_json ? JSON.parse(t.reblog_emoji_json) : undefined; } catch { return undefined; } })(),
    369       } : undefined,
     382      } : undefined),
    370383      // Whether THIS account already liked/boosted the note, so the app's
    371384      // detail-view buttons show the current state (and can toggle/undo).
     
    384397  const me = AP.actorId(base, auth.site.slug);
    385398  const myHandle = (() => { try { return `@${auth.site.slug}@${new URL(base).host}`; } catch { return `@${auth.site.slug}`; } })();
    386   const messages = AP.getDirectMessages(auth.site.slug, 60).map((m) => ({
     399  // Messages dicht (shaer-3ow) sluit vreemden en vrienden, maar NOOIT het
     400  // guardian-kanaal: de zwaai en het gesprek na een hulpvraag zijn precies
     401  // het kanaal dat het kind veilig houdt, en een poort die dat afsnijdt
     402  // beschermt niemand. De hulpvraag zelf gaat aan de innamekant al altijd voor.
     403  const guardianUris = (() => { try { return new Set(Guardianship.listGuardians(auth.site.slug).map((g) => g.other_uri)); } catch { return new Set(); } })();
     404  const messages = AP.getDirectMessages(auth.site.slug, 60)
     405    .filter((m) => messagesAllowed || m.help_request || guardianUris.has(m.actor_uri))
     406    .map((m) => ({
    387407    id: `${m.object_uri}#create`,
    388408    type: 'Create',
     
    402422      // groups the note into a conversation. No FEP-e232 link tags here: a
    403423      // mention row keeps the resolved quote, not the raw tags.
    404       tag: [{ type: 'Mention', href: me, name: myHandle }, ...(AP.timelineEmojis(m.emoji_json) || [])],
    405       attachment: AP.timelineAttachments(m.media_json),
     424      tag: [{ type: 'Mention', href: me, name: myHandle }, ...(emojiAllowed ? (AP.timelineEmojis(m.emoji_json) || []) : [])],
     425      attachment: AP.gateAttachments(AP.timelineAttachments(m.media_json), { images: imagesAllowed, audio: musicAllowed }),
    406426      // FEP-633c: what kind of message this is. The wave is a gentle nudge from
    407427      // a guardian; the help request is the buoy. Both render differently.
    408428      'shaer:wave': m.wave ? true : undefined,
    409429      'shaer:helpRequest': m.help_request ? true : undefined,
    410       'shaer:quote': AP.timelineQuote(m.quote_json),
    411       'shaer:author': (m.actor_name || m.actor_handle || m.actor_icon) ? {
     430      'shaer:quote': quotesAllowed ? AP.timelineQuote(m.quote_json) : undefined,
     431      'shaer:author': gateAuthor((m.actor_name || m.actor_handle || m.actor_icon) ? {
    412432        name: m.actor_name || undefined, handle: m.actor_handle || undefined,
    413433        icon: m.actor_icon || undefined, url: m.actor_url || undefined,
    414434        emojis: (() => { try { return m.actor_emoji_json ? JSON.parse(m.actor_emoji_json) : undefined; } catch { return undefined; } })(),
    415       } : undefined,
     435      } : undefined),
    416436      'shaer:embed': embedsAllowed ? AP.timelineEmbed(m.embed_json, { playback: playbackAllowed }) : undefined,
    417437    },
     
    475495      // and not just the picture over it.
    476496      'shaer:externalLinks': playbackAllowed,
     497      // De rest van de familie (8-8): de app hoort VOORAF te weten wat hij mag
     498      // aanbieden in plaats van het bij de eerste weigering te ontdekken. De
     499      // (+) kaart leest shaer:compose al (Barts gate); de rest is er voor de
     500      // schermen die nog komen. Serveren wat waar is kost hier niets.
     501      'shaer:compose': composeAllowed,
     502      'shaer:messages': messagesAllowed,
     503      'shaer:images': imagesAllowed,
     504      'shaer:music': musicAllowed,
     505      'shaer:quoteCards': quotesAllowed,
     506      'shaer:customEmoji': emojiAllowed,
     507      'shaer:externalThreads': threadsAllowed,
    477508    },
    478509    // Het merk van wat hierin zit. Geef hem terug als `since` om op het
     
    721752  if (!/^https:\/\//i.test(objectUri)) return res.status(400).json({ error: 'object must be an https URI' });
    722753  const isWard = (() => { try { return Guardianship.listGuardians(auth.site.slug).length > 0; } catch { return false; } })();
    723   const uit = await AP.getThread(auth.site.slug, objectUri, { isWard });
     754  const uit = await AP.getThread(auth.site.slug, objectUri);
    724755  if (!uit.found) return res.status(404).json({ error: 'note not reachable' });
     756  // De poortstand komt uit de kolom (shaer-9y2): expliciete 0/1 van de
     757  // guardians wint, de automatiek is dicht-voor-een-ward. Dicht is de KRING,
     758  // niet niets: antwoorden van al goedgekeurd volk blijven staan, en wat er
     759  // buiten valt wordt geteld. Beeld, muziek en emoji gaan door dezelfde
     760  // poorten als de tijdlijn -- per verzoek, buiten de threadcache om.
     761  const threadsOpen = Guardianship.wardGateAllowed(auth.site.external_threads, isWard);
     762  const gate2 = (col) => Guardianship.wardGateAllowed(auth.site[col], isWard);
     763  const kring = threadsOpen ? { notes: uit.notes, hidden: 0 } : AP.filterThreadToCircle(auth.site.slug, uit.notes);
     764  const imagesOk = gate2('gate_images'), musicOk = gate2('gate_music'), emojiOk = gate2('gate_custom_emoji');
     765  uit.notes = kring.notes.map((n) => ({
     766    ...n,
     767    attachment: AP.gateAttachments(n.attachment, { images: imagesOk, audio: musicOk }),
     768    tag: emojiOk ? n.tag : AP.stripEmojiTags(n.tag),
     769    'shaer:author': (n['shaer:author'] && !emojiOk) ? { ...n['shaer:author'], emojis: undefined } : n['shaer:author'],
     770  }));
     771  uit.hidden = kring.hidden;
    725772  // Liked/boosted per antwoord, BUITEN de cache om: de genormaliseerde notes
    726773  // mogen twee minuten oud zijn, maar of JIJ iets geliked hebt hoort van nu te
Note: See TracChangeset for help on using the changeset viewer.