Changeset 88d7c8f in Klonkt for src/routes/guardian.js


Ignore:
Timestamp:
07/29/2026 07:15:16 AM (6 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
e27b8db
Parents:
439f095
Message:

Een gated voorstel bereikte de andere guardians nooit

Op de vloot nagekeken waarom YouTube-voorbeelden bij beta uit blijven: het
voorstel van sound-fabrics staat er, met een ja van een van de drie guardians,
en het is stil verlopen. Niet omdat iemand bezwaar had, maar omdat niemand
anders het ooit gezien heeft.

Het voorstel wordt geadresseerd aan de server van het kind, want die telt en
handhaaft (5.6). Maar daarmee bereikt het alleen de voorsteller en het kind. De
twee guardians op andere servers weten van niets, kunnen dus niet antwoorden, en
een drempel van twee is onhaalbaar. Elk voorstel verloopt na een dag.

De ontbrekende schakel is het doorsturen, precies wat 5.3 al doet voor een
gated follow: de server van het kind kent de gezaghebbende guardian-lijst, dus
die stuurt het voorstel door. Elke guardian bewaart een kopie die hij kan
beantwoorden, en het antwoord reist terug naar het kind, dat telt.

Changed files:
src/config/database.js

  • tabel ap_gated_reviews, de guardian-kopie (zelfde vorm als ap_follow_reviews)

src/services/guardianship/gated.js

  • de kopie-opslag: bewaren, lezen, beantwoorden, opruimen

src/services/guardianship/handshake.js

  • ward-kant: doorsturen naar de andere guardians zodra het voorstel openstaat
  • guardian-kant: de doorgestuurde kopie bewaren om te kunnen antwoorden

src/routes/guardian.js

  • gatedReviews in de dashboardstaat; POST /guardian/api/gated/:id stuurt het antwoord naar de inbox van het kind

src/assets/js/guardian.js, src/assets/css/guardian.css

src/services/i18n.js

  • de teksten in nl, en, de

test/gated-settings.test.js

  • de hele keten: voorstellen, doorsturen naar allebei de anderen, de kopie opslaan, antwoorden, en pas bij twee van drie gaat de gate open

remarks: dit forceert niets; het maakt alleen mogelijk wat de spec al bedoelde.
Twee van de drie guardians moeten nog steeds akkoord gaan, en het venster is
24 uur.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    r439f095 r88d7c8f  
    5252    'avail_available', 'avail_away', 'avail_dormant', 'panel_guards', 'panel_guards_remote',
    5353    'lapse_propose', 'lapse_line', 'lapse_tally', 'lapse_note', 'lapse_agree', 'lapse_disagree', 'voted',
    54     'away_title', 'away_sub', 'away_week', 'away_month', 'away_done'];
     54    'away_title', 'away_sub', 'away_week', 'away_month', 'away_done',
     55    // A gated-setting proposal from a fellow guardian (5.6).
     56    'gated_title', 'gated_line_on', 'gated_line_off', 'gated_agree', 'gated_disagree'];
    5557  const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)]));
    5658  s.wave = i18nT(L, 'guardian.wave');
     
    9395    // Running lapses (3.6.3) this guardian or its local wards are party to.
    9496    lapses: Guardianship.availability.lapseQueueItems(site.slug, me, Date.now()),
     97    // Gated-setting proposals another guardian opened on a ward we share
     98    // (5.6), forwarded here by the ward's server. Without answering these the
     99    // threshold is never met and the proposal simply expires.
     100    gatedReviews: Guardianship.gated.listGatedReviews(site.slug).map((r) => ({
     101      id: r.id, ward: r.ward_uri, proposer: r.proposer, feature: r.feature, value: !!r.value,
     102    })),
    95103    help,
    96104    strings: uiStrings(L),
     
    345353  if (!r || r.status >= 400) return res.status(r?.status || 500).json({ error: r?.error || 'lapse_failed' });
    346354  res.json({ ok: true, lapse: r.id });
     355});
     356
     357// ── Answer a forwarded gated-setting proposal (FEP-633c 5.6) ─────────────
     358// The decision belongs to the ward's server, so the answer travels there as an
     359// Accept/Reject on the offer id, exactly like a gated follow's decision.
     360router.post('/api/gated/:id', requireAuth, express.json({ limit: '2kb' }), async (req, res) => {
     361  const site = siteForUser(req);
     362  if (!site) return res.status(404).json({ error: 'no_site' });
     363  const review = Guardianship.gated.getGatedReview(site.slug, req.params.id);
     364  if (!review) return res.status(404).json({ error: 'gone' });
     365  const agree = req.body?.answer !== 'reject';
     366  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
     367  const me = AP.actorId(base, site.slug);
     368  const activity = {
     369    id: `${me}#gated-${Date.now().toString(36)}`,
     370    type: agree ? 'Accept' : 'Reject', actor: me, to: [review.ward_uri], object: review.id,
     371  };
     372  try { await AP.deliverToActor(site, review.ward_uri, activity); }
     373  catch { return res.status(502).json({ error: 'delivery' }); }
     374  Guardianship.gated.removeGatedReview(site.slug, review.id);
     375  res.json({ ok: true, answer: agree ? 'accept' : 'reject' });
    347376});
    348377
Note: See TracChangeset for help on using the changeset viewer.