Changeset 6d5ce0c in Klonkt for src/routes


Ignore:
Timestamp:
07/29/2026 10:50:32 AM (6 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
08ab8ad
Parents:
c8e03c6
Message:

Op deze machine gedraagt elke Klonkt zich alsof hij ergens anders staat

Robins regel, en de reden staat in de logs van deze week. Twee bugs kwamen uit
hetzelfde patroon: een tweede, lokale route die een kapotte externe route
verborg. De Undo bereikte een kind op dezelfde machine nooit, en het gated
voorstel was een maand stuk over de lijn terwijl de sluiproute de stem hier
direct opschreef en het dashboard er prima uitzag.

Samenlokatie is nu een kwestie van TRANSPORT, geen beslispad. deliverToActor
geeft een activiteit voor een lokale ontvanger door aan dezelfde inbox-handler
die de lijn zou bereiken, inclusief de controle of de ondertekenaar de afzender
is. Alles daarboven weet het verschil niet meer, en dus draait elke deployment
dezelfde code.

Directe berichten deden dat nog niet. Die zochten een inbox op en POSTten
erheen, dus een bericht aan een kind op deze machine ging naar onze eigen
hostnaam en terug, of nergens heen. Nu nemen ze dezelfde loopback.

En de kern van het probleem zat in de inbox zelf: "van onze eigen actor" werd
gelezen als "van wie dan ook op deze machine". Daardoor werd elk bericht tussen
twee sites op een instantie met een 202 aangenomen en daarna weggegooid: geen
vermelding, geen afwezigheid, geen hulpvraag. Buren zijn niet wij.

Daarmee konden twee met de hand geschreven sluiproutes weg: het lokaal
wegschrijven van een afwezigheid in de C2S-outbox en in de Guardian PWA. Die
bestonden alleen omdat de echte weg niet aankwam.

Changed files:
src/services/ActivityPubService.js

  • isLocalActor is nu "de eigenaar van deze inbox", niet "iemand op deze host"
  • localActor(): het actordocument van een site die wij hosten, uit onze eigen database in plaats van via een verzoek aan onszelf
  • de lokale sluiproute voor afwezigheid in de C2S-outbox is weg

src/services/guardianship/delivery.js

  • een lokale ontvanger krijgt het bericht via de loopback, de rest per inbox
  • een lokale ontvanger wordt lokaal opgezocht, dus hij valt niet stilletjes uit de ontvangerslijst als het verzoek aan onszelf mislukt

src/routes/guardian.js

  • /api/away schrijft niets meer zelf weg: het bericht doet het werk

src/services/guardianship/handshake.js

  • commentaar bijgewerkt bij de plekken die wel lokaal mogen schrijven

New file:
test/co-location.test.js

  • hetzelfde scenario twee keer, alles-lokaal en alles-extern, met de eis dat de eindtoestand gelijk is
  • een afwezigheid via de loopback en dezelfde brief van de lijn gelezen
  • de loopback weigert nog steeds een afzender die niet klopt
  • een bewaking die faalt zodra er een nieuwe lokale sluiproute in een beslispad verschijnt, met de legitieme uitzonderingen bij naam

remarks: 329 tests groen, en de server start. Twee dingen om te weten voor de
uitrol: sites op een instantie die elkaar volgen zien elkaars berichten nu wel
(dat is wat volgen betekent, maar het is zichtbaar anders), en gated follows
lopen voor een lokale guardian nog steeds via de gedeelde database. Die laatste
staat met naam en toenaam in de bewakingstest, zodat hij niet vergeten wordt.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    rc8e03c6 r6d5ce0c  
    309309//    handshake module decides when it commits (§3.1).
    310310// ── Step away (FEP-633c 3.6.1): the guardian declares itself unavailable ──
    311 // One direct note with shaer:away and an endTime to every ward, the same
    312 // path Shaer takes over C2S. Wards on this instance are applied directly (a
    313 // local inbox never receives its own delivery); the rest travels S2S.
     311// One direct note with shaer:away and an endTime to every ward, the same path
     312// Shaer takes over C2S, and the only path: a ward on this instance receives
     313// that note through the loopback and applies the absence in its own inbox
     314// handler, exactly as a ward elsewhere does. This route used to write the
     315// local wards itself as well, which meant the wire version could break without
     316// anyone here noticing.
    314317router.post('/api/away', requireAuth, express.json({ limit: '2kb' }), async (req, res) => {
    315318  const site = siteForUser(req);
     
    320323  if (!wards.length) return res.status(409).json({ error: 'no_wards' });
    321324  const until = Date.now() + days * 24 * 3600 * 1000;
    322   const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
    323   const me = AP.actorId(base, site.slug);
    324   let applied = 0;
    325   for (const uri of wards) {
    326     const wslug = uri.startsWith(`${base}/`) ? uri.replace(/\/+$/, '').split('/').pop() : null;
    327     if (wslug && Guardianship.listGuardians(wslug).some((g) => g.other_uri === me)) {
    328       Guardianship.availability.declareAway(wslug, me, until);
    329       applied++;
    330     }
    331   }
    332325  const L = resolveLang(req);
    333326  const text = i18nT(L, 'guardian.away_msg', { date: new Date(until).toLocaleDateString('nl-NL') });
    334327  const r = await AP.deliverDirectNote(site, { recipients: wards, text, awayUntil: until }).catch(() => null);
    335   if (!applied && !(r && r.id)) return res.status(502).json({ error: 'away_failed' });
     328  if (!(r && r.id)) return res.status(502).json({ error: 'away_failed' });
    336329  res.json({ ok: true, until });
    337330});
     
    508501  const offerId = `${me}/gated/${Date.now().toString(36)}${Math.floor(Math.random() * 1e4).toString(36)}`;
    509502  const offer = Guardianship.gated.buildGatedOffer(offerId, me, uri, feature, allow);
     503  // ONE path, whether the ward lives here or on the other side of the world
     504  // (Robins regel, 29-7): propose over the wire and let the ward's server do
     505  // what it does for everyone. deliverToActor loops a local recipient back
     506  // into the same inbox handler, so co-location changes the transport and
     507  // nothing else. The old shortcut recorded the vote here directly, which is
     508  // how the remote path stayed broken for a month without anyone noticing.
     509  AP.deliverToActor(site, uri, offer).catch(() => { /* queued, best-effort */ });
    510510  const localSlug = (base && uri.startsWith(`${base}/`)) ? uri.replace(/\/+$/, '').split('/').pop() : null;
    511   const localWard = localSlug ? db.prepare('SELECT slug FROM sites WHERE slug = ?').get(localSlug) : null;
    512   if (localWard) {
    513     Guardianship.gated.rememberGatedOffer(offerId, localWard.slug, feature, allow);
    514     const r = Guardianship.gated.recordGatedVote(localWard.slug, feature, me, allow);
    515     // Same forward as the S2S path: without it the other guardians never learn
    516     // the proposal exists and a threshold of two can never be met.
    517     if (r.state === 'open') {
    518       const wardActor = AP.actorId(base, localWard.slug);
    519       for (const g of Guardianship.listGuardians(localWard.slug).map((x) => x.other_uri)) {
    520         if (g === me) continue;
    521         // Signed by the ward, so the body must say the ward: anything else is
    522         // a signer mismatch and the receiver answers 401 (as it should).
    523         AP.deliverToActor(
    524           db.prepare('SELECT * FROM sites WHERE slug = ?').get(localWard.slug),
    525           g,
    526           { ...offer, actor: wardActor, to: [g], 'shaer:proposer': me },
    527         ).catch(() => { /* queued */ });
    528       }
    529     }
    530     return res.json({ ok: true, allow, state: r.state, need: r.need, of: r.of });
    531   }
    532   AP.deliverToActor(site, uri, offer).catch(() => { /* queued, best-effort */ });
    533   res.json({ ok: true, allow, state: 'open', federated: true });
     511  const progress = localSlug ? Guardianship.gated.gatedProgress(localSlug, feature) : null;
     512  res.json({ ok: true, allow, state: 'open', ...(progress || { federated: true }) });
    534513}
    535514
Note: See TracChangeset for help on using the changeset viewer.