Changeset 780a7c6 in Klonkt for src/assets/js/guardian.js


Ignore:
Timestamp:
07/24/2026 07:44:15 PM (7 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
b5924eb
Parents:
c26cc18
Message:

Guardianship Fase 0+1: de echte multi-party handshake (FEP-633c §3)

De eerste versie committeerde na één accept. Nu de spec: geen enkele partij
maakt een voogdij alleen, en een nieuwe guardian erbij kan niet zonder
toestemming van de bestaande. Daemon als blauwdruk, zodat Klonkt en de
test-daemon exact hetzelfde gedragen en de Shaer-clients één contract lezen.

Fase 0 (datamodel): ap_guardian_offers (per lokale partij een kopie van de
handshake, PK slug+offer_id) + ap_guardian_offer_accepts (de accept-tally).
ap_guardianships houdt alleen nog de GECOMMITTE relaties.

Fase 1 (state-machine): offers.js is een getrouwe port van de daemon-Handshake
(accepts over ward+candidate+existing; ready = ward && candidate && (geen
existing OF >=1 existing); een Reject voidt). handshake.js orchestreert het
gedistribueerd: de kandidaat adresseert de Offer aan ward + alle bestaande
guardians (§3.1.1); elke Accept wordt aan alle andere partijen gebroadcast, dus
elke instance-kopie convergeert; zodra een kopie compleet is committeert die
lokaal (ward schrijft shaer:guardians, guardian schrijft z'n ward), met de
kandidaat-inbox als handle (§6). Volgorde-onafhankelijk.

Ook: §1 wederzijdse uitsluiting (een ward is nooit ook guardian in het
actor-doc), de queues vullen nu de echte accept-tally (needsMyAccept/
readyToCommit/acceptedBy/existingGuardians), en de PWA + Berichten beantwoorden
via de C2S Accept/Reject-pijplijn per offer-id. De co-guardian ziet een
mede-voogdij-aanvraag met accepteer/weiger in de PWA.

Changed files:
src/config/database.js

  • tabellen ap_guardian_offers + ap_guardian_offer_accepts

src/services/guardianship/offers.js (NEW)

  • de handshake-state-machine (daemon-port), per-instance in SQLite

src/services/guardianship/relations.js

  • alleen commit-writers + actor-props (§1 uitsluiting)

src/services/guardianship/handshake.js

  • gedistribueerde multi-party C2S/S2S orchestratie

src/services/guardianship/queues.js

  • offers-queue uit de state-machine

src/services/guardianship/index.js

  • exports bijgewerkt

src/services/ActivityPubService.js

  • wire localSlug + fetchActor; inbound-routing naar alle lokale partijen

src/routes/guardian.js

  • dashboard toont offers met tally; POST /guardian/offer (accept/reject)

src/routes/posts.js

  • Berichten toont ward-offers uit de state-machine; accept via offer-id

src/views/pages/messages.ejs, src/assets/js/guardian.js, src/assets/css/guardian.css

  • offer-kaarten per state (mijn aanvraag / mede-voogdij / wachten)

src/services/i18n.js

  • accept/reject/complete/coguard + co-guardian push (nl/en/de)

test/guardianship.test.js

  • multi-party: eerste guardian, co-approval bestaande guardian, reject voidt, ward-mag-niet-guarden, vaste initiator

remarks: Fase 2 (follow-gating), 3 (hasGuardians + Not-a-Teapot), 4 (Undo/
emancipatie) volgen. 164 tests groen.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/assets/js/guardian.js

    rc26cc18 r780a7c6  
    4747  }
    4848
    49   // ── 3. Pending offers I sent ───────────────────────────────────────────
     49  // ── 3. Offers I am a party to (sent, or a co-guardianship to co-approve) ─
     50  function answer(offerId, decision, btn) {
     51    if (btn) btn.disabled = true;
     52    fetch('/guardian/offer', {
     53      method: 'POST', headers: { 'Content-Type': 'application/json' },
     54      body: JSON.stringify({ offer: offerId, answer: decision, site: S.site }),
     55    }).then(refresh);
     56  }
     57  function offerCard(o) {
     58    var card = el('div', 'g-card');
     59    var row = el('div', 'row');
     60    var subject = o['shaer:iAmCandidate']
     61      ? handleOf(o['shaer:ward'], o['shaer:wardHandle'])            // my sent offer: about the ward
     62      : handleOf(o['shaer:candidate'], o['shaer:candidateHandle']); // co-guard: who wants in
     63    row.appendChild(el('span', 'who grow', subject));
     64    if (o['shaer:iAmCandidate']) {
     65      // My own offer, waiting for the others to accept.
     66      row.appendChild(el('span', 'tag wait', T.pending));
     67      var rt = el('button', 'quiet small', T.retract);
     68      rt.addEventListener('click', function () { answer(o.id, 'reject', rt); });
     69      row.appendChild(rt);
     70    } else if (o['shaer:needsMyAccept']) {
     71      // A co-guardianship offer for a ward I already guard: my call.
     72      row.appendChild(el('span', 'tag co', T.coguard));
     73      var ac = el('button', 'small', T.accept);
     74      ac.addEventListener('click', function () { answer(o.id, 'accept', ac); });
     75      var rj = el('button', 'quiet small', T.reject);
     76      rj.addEventListener('click', function () { answer(o.id, 'reject', rj); });
     77      row.appendChild(ac); row.appendChild(rj);
     78    } else {
     79      row.appendChild(el('span', 'tag wait', T.awaiting_others));
     80    }
     81    card.appendChild(row);
     82    return card;
     83  }
    5084  function renderPending() {
    5185    var list = document.getElementById('pending-list');
    5286    list.textContent = '';
    53     var pend = S.pendingOffers || [];
    54     pend.forEach(function (w) {
    55       var card = el('div', 'g-card');
    56       var row = el('div', 'row');
    57       row.appendChild(el('span', 'who grow', handleOf(w.other_uri, w.other_handle)));
    58       row.appendChild(el('span', 'tag wait', T.pending));
    59       var btn = el('button', 'quiet small', T.retract);
    60       btn.addEventListener('click', function () { remove(w.other_uri, btn); });
    61       row.appendChild(btn);
    62       card.appendChild(row);
    63       list.appendChild(card);
    64     });
    65     show('pending-section', pend.length > 0);
     87    var offers = S.offers || [];
     88    offers.forEach(function (o) { list.appendChild(offerCard(o)); });
     89    show('pending-section', offers.length > 0);
    6690  }
    6791
Note: See TracChangeset for help on using the changeset viewer.