Changeset 2bfe26c in Klonkt for src/routes/guardian.js


Ignore:
Timestamp:
08/08/2026 07:00:17 AM (5 weeks ago)
Author:
roboburr <roboburr@…>
Branches:
main
Children:
0b0cd54
Parents:
f3a2556
git-author:
Robin <roboburr@…> (08/08/2026 07:00:15 AM)
git-committer:
roboburr <roboburr@…> (08/08/2026 07:00:17 AM)
Message:

Voorstellen vanuit de app, en een kind dat zelf kan vragen (shaer-8ru)

Barts opdracht: bouw shaer-8ru zodat de apps ook kunnen voorstellen, en maak
daarnaast het pad voor de ward om zelf een voorstel aan te zwengelen als hij
tegen een gated feature aanloopt.

DEEL 1 -- DE APPS KUNNEN VOORSTELLEN. De outbox kent nu een Offer van een
shaer:GatedSetting, precies de vorm die 5.6 al beschrijft en die de inbox al las.
De afweging zelf is verhuisd naar AP.proposeGate, waar de PWA-route nu ook
doorheen loopt. Twee implementaties naast elkaar zou een tweede weg naar
hetzelfde besluit zijn -- exact de fout die we vanmiddag bij de antwoordpoort
rechtzetten, toen de innamepoort alleen in C2S bleek te zitten.

DEEL 2 -- HET KIND KRIJGT WOORDEN. Tot nu toe liep alles over de guardians: zij
zien de catalogus, zij stellen voor, zij tellen. Het kind liep tegen een dichte
deur en kon niets. Nu stuurt het een shaer:gateRequest, en die landt bij zijn
guardians BIJ DE POORT waar hij over gaat -- niet in een aparte lijst die je
apart moet openen en dus vergeet.

EEN VRAAG IS GEEN STEM, en dat is de hele grens. Het verzoek is geen voorstel en
telt nergens mee; pas als een guardian het oppakt wordt het een voorstel dat
langs de gewone tally gaat. Anders opent een kind zijn eigen poort door hard
genoeg te vragen. Er staat een toets op die kijkt of er echt geen voorstel en
geen open poort uit ontstaat.

NIET DE REDDINGSBOEI. Een hulpvraag is een noodgeval en gaat door elke dichte
deur; dit is een wens. Ze door elkaar laten lopen zou de boei devalueren tot "het
kind wil iets", en dan kijkt er op een dag niemand meer op als hij afgaat.

GEEN VRIJE TEKST, en dat is geen gierigheid maar de reden dat het verzoek langs
de messages-poort MAG. Een kind met berichten dicht kan nog steeds om iets
vragen, zonder dat er een kanaal ontstaat om omheen die poort te praten. Wil het
uitleggen waarom, dan is dat een gesprek, en gesprekken hebben hun eigen poort.

Alleen van een eigen ward: een verzoek van een vreemde is geen vraag maar een
onbekende die iets over jouw instellingen wil zeggen.

Twaalf toetsen. Twee mutaties gecontroleerd (1 en 6 rood). Suite 712/712.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    rf3a2556 r2bfe26c  
    620620  const site = siteForUser(req);
    621621  if (!site) return res.status(404).json({ error: 'no_site' });
    622   const uri = String(req.body?.uri || '').trim();
    623   const allow = req.body?.allow === true;
    624   if (!uri) return res.status(400).json({ error: 'empty_uri' });
    625   // Only a guardian of this ward, and only for a ward we host: a setting on a
    626   // remote ward belongs to that ward's own server (federating it is Fase 4).
    627   const isMyWard = Guardianship.listWards(site.slug).some((w) => w.other_uri === uri);
    628   if (!isMyWard) return res.status(403).json({ error: 'not_your_ward' });
    629   // §5.6: propose it to the WARD'S server, wherever that is. The ward's server
    630   // tallies (a majority of its guardians, §3.5) and enforces. Co-location is
    631   // just the case where that server happens to be this one, so it takes the
    632   // same road: propose, then let the tally decide. Anything else would make a
    633   // guardian on the ward's own instance more powerful than one elsewhere.
    634   const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
    635   const me = AP.actorId(base, site.slug);
    636   const feature = req.body.feature;   // normalised by the route above
    637   const offerId = `${me}/gated/${Date.now().toString(36)}${Math.floor(Math.random() * 1e4).toString(36)}`;
    638   const offer = Guardianship.gated.buildGatedOffer(offerId, me, uri, feature, allow);
    639   // ONE path, whether the ward lives here or on the other side of the world
    640   // (Robins regel, 29-7): propose over the wire and let the ward's server do
    641   // what it does for everyone. deliverToActor loops a local recipient back
    642   // into the same inbox handler, so co-location changes the transport and
    643   // nothing else. The old shortcut recorded the vote here directly, which is
    644   // how the remote path stayed broken for a month without anyone noticing.
    645   // Our own record of what we sent (5.6): the ward's server answers this Offer
    646   // once the decision settles, and that answer needs a row to land in. It is
    647   // also the only way the proposer's screen can say more than a button caption.
    648   Guardianship.gated.recordSent(offerId, site.slug, uri, feature, allow);
    649   AP.deliverToActor(site, uri, offer).catch(() => { /* queued, best-effort */ });
    650   const localSlug = (base && uri.startsWith(`${base}/`)) ? uri.replace(/\/+$/, '').split('/').pop() : null;
    651   const progress = localSlug ? Guardianship.gated.gatedProgress(localSlug, feature) : null;
    652   res.json({ ok: true, allow, state: 'open', ...(progress || { federated: true }) });
     622  // De hele afweging staat in AP.proposeGate, zodat de apps langs dezelfde weg
     623  // kunnen voorstellen (shaer-8ru). Deze route is nog maar de PWA-deur ernaartoe.
     624  const uit = AP.proposeGate(site, req.body?.uri, req.body?.feature, req.body?.allow === true);
     625  const { status, ...rest } = uit;
     626  return res.status(status === 200 ? 200 : status).json(rest);
    653627}
    654628
Note: See TracChangeset for help on using the changeset viewer.