Changeset 6d5ce0c in Klonkt for src/routes/guardian.js
- Timestamp:
- 07/29/2026 10:50:32 AM (6 weeks ago)
- Branches:
- main
- Children:
- 08ab8ad
- Parents:
- c8e03c6
- File:
-
- 1 edited
-
src/routes/guardian.js (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/guardian.js
rc8e03c6 r6d5ce0c 309 309 // handshake module decides when it commits (§3.1). 310 310 // ── 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. 314 317 router.post('/api/away', requireAuth, express.json({ limit: '2kb' }), async (req, res) => { 315 318 const site = siteForUser(req); … … 320 323 if (!wards.length) return res.status(409).json({ error: 'no_wards' }); 321 324 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 }332 325 const L = resolveLang(req); 333 326 const text = i18nT(L, 'guardian.away_msg', { date: new Date(until).toLocaleDateString('nl-NL') }); 334 327 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' }); 336 329 res.json({ ok: true, until }); 337 330 }); … … 508 501 const offerId = `${me}/gated/${Date.now().toString(36)}${Math.floor(Math.random() * 1e4).toString(36)}`; 509 502 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 */ }); 510 510 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 }) }); 534 513 } 535 514
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)