Changeset 88d7c8f in Klonkt for test/gated-settings.test.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
  • test/gated-settings.test.js

    r439f095 r88d7c8f  
    8282  assert.equal(parseGatedSetting({ type: 'Relationship' }), null, 'a different Offer is not ours');
    8383});
     84
     85// The leg that was missing, found on the live fleet: a proposal addressed to
     86// the ward's server reached only the proposer and the ward. The two guardians
     87// on other servers never learned it existed, so the threshold of two could
     88// never be met and every proposal expired unanswered. Link previews for beta
     89// stayed off not because anyone objected, but because nobody could answer.
     90test('the ward forwards a proposal to the other guardians, or nobody can answer', async () => {
     91  const dbMod2 = await import('../src/config/database.js');
     92  const database = dbMod2.default;
     93  const G = await import('../src/services/guardianship/index.js');
     94  const WARD = 'https://test.example/ap/users/kid9';
     95  const [A, B, C] = ['https://a.test/u/a', 'https://b.test/u/b', 'https://c.test/u/c'];
     96  database.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u9', 'u9', 'u9@t', 'x', 'god');
     97  database.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,0)').run('s9', 'kid9', 'kid9', 'u9');
     98  for (const g of [A, B, C]) {
     99    database.prepare("INSERT OR IGNORE INTO ap_guardianships (slug, role, other_uri, status, offer_id) VALUES ('kid9','ward',?, 'accepted','o')").run(g);
     100  }
     101  const sent = [];
     102  G.wireHandshake({
     103    selfId: (slug) => `https://test.example/ap/users/${slug}`,
     104    localSlug: (u) => (u.startsWith('https://test.example/ap/users/') ? u.split('/').pop() : null),
     105    deriveHandle: (u) => '@' + u.split('/').pop(),
     106    fetchActor: async (u) => ({ id: u, inbox: `${u}/inbox` }),
     107    deliverTo: async (s, to, act) => { sent.push({ to, act }); return { delivered: true }; },
     108    onEvent: null,
     109  });
     110  const site = database.prepare('SELECT * FROM sites WHERE slug = ?').get('kid9');
     111
     112  // A proposes. The ward records A's own vote (1 of 3, threshold 2: open).
     113  const offer = G.gated.buildGatedOffer('https://a.test/gated/1', A, WARD, 'shaer:externalEmbeds', true);
     114  assert.equal(await G.handleGuardianshipInbox(site, { ...offer, actor: A }), true);
     115  assert.equal(database.prepare('SELECT external_embeds FROM sites WHERE slug = ?').get('kid9').external_embeds, null, 'one voice is not a majority');
     116
     117  // The forward: B and C are told, A is not asked twice.
     118  const told = sent.filter((x) => x.act.object && x.act.object['shaer:feature']).map((x) => x.to).sort();
     119  assert.deepEqual(told, [B, C].sort(), 'both other guardians must receive the proposal');
     120
     121  // B receives its copy on its own server and can answer it.
     122  database.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id, is_primary) VALUES (?,?,?,?,0)').run('s10', 'gb', 'gb', 'u9');
     123  database.prepare("INSERT OR IGNORE INTO ap_guardianships (slug, role, other_uri, status, offer_id) VALUES ('gb','guardian',?, 'accepted','o')").run(WARD);
     124  const gbSite = database.prepare('SELECT * FROM sites WHERE slug = ?').get('gb');
     125  assert.equal(await G.handleGuardianshipInbox(gbSite, { ...offer, actor: A, to: ['https://test.example/ap/users/gb'] }), true);
     126  const review = G.gated.listGatedReviews('gb')[0];
     127  assert.ok(review, 'the guardian stores a copy it can answer');
     128  assert.equal(review.feature, 'shaer:externalEmbeds');
     129  assert.equal(review.ward_uri, WARD);
     130
     131  // B's Accept reaches the ward: 2 of 3, the threshold, and the gate opens.
     132  assert.equal(await G.handleGuardianshipInbox(site, { type: 'Accept', actor: B, object: 'https://a.test/gated/1' }), true);
     133  assert.equal(database.prepare('SELECT external_embeds FROM sites WHERE slug = ?').get('kid9').external_embeds, 1,
     134    'two of three agreed, so the ward may see link previews');
     135});
Note: See TracChangeset for help on using the changeset viewer.