Changeset 3ffbedd in Klonkt


Ignore:
Timestamp:
07/24/2026 08:28:15 PM (7 weeks ago)
Author:
Robin Genis <roboburr@…>
Branches:
main
Children:
fcd6964
Parents:
c502242
Message:

Guardianship: eerste guardian committeert op de enkele ward-accept

Het offer ís de instemming van de kandidaat. Een vrij account mag in één keer
een guardian aannemen: er is nog geen tweede guardian om mee te akkoorderen.
Pas als het een ward is, treedt de co-approval in werking.

Concreet: de kandidaat-accept wordt nu impliciet vastgelegd bij het offer, op
elke kopie (C2S bij het versturen, S2S bij ontvangst). Zo committeert de eerste
guardian zodra de ward accepteert; een tweede guardian erbij vereist nog steeds
dat een bestaande guardian mee-accepteert (§3.1.2). Geen aparte "voltooien"-stap
voor de kandidaat meer.

Changed files:
src/services/guardianship/handshake.js

  • Offer legt de kandidaat-accept vast (C2S + S2S)

test/guardianship.test.js

  • eerste guardian: commit op enkele ward-accept; tweede: co-approval

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

Files:
2 edited

Legend:

Unmodified
Added
Removed
  • src/services/guardianship/handshake.js

    rc502242 r3ffbedd  
    107107      wardHandle: deps.deriveHandle(rel.ward), candidateHandle: deps.deriveHandle(me),
    108108    });
     109    // The Offer IS the candidate's agreement to serve: record it as the
     110    // candidate's accept. So a FREE ward commits on its own single accept (no
     111    // second guardian to co-approve yet); once it IS a ward, adding another
     112    // guardian still needs an existing guardian to co-accept.
     113    offers.recordAccept(site.slug, offerId, me);
    109114    // Addressed to the ward AND every existing guardian (§3.1.1).
    110115    const recipients = [rel.ward, ...existing];
     
    159164      wardHandle: deps.deriveHandle(rel.ward), candidateHandle: deps.deriveHandle(rel.candidate),
    160165    });
     166    // The Offer carries the candidate's agreement (see the C2S side): record it
     167    // so this copy's tally matches — a free ward then commits on its own accept.
     168    offers.recordAccept(site.slug, idOf(activity), rel.candidate);
    161169    notify(site.slug, { kind: rel.ward === me ? 'offer_received' : 'offer_for_ward', ward: rel.ward, candidate: rel.candidate });
    162170    return true;
  • test/guardianship.test.js

    rc502242 r3ffbedd  
    4343const offerIdFrom = (r) => r.id;
    4444
    45 test('first guardian: candidate offers, ward accepts, candidate completes', async () => {
     45test('first guardian: a free ward commits on its own single accept', async () => {
    4646  const off = await G.handleGuardianshipOutbox(parent, {
    4747    type: 'Offer', object: { type: 'Relationship', subject: KID, relationship: 'shaer:Guardian', object: ME },
     
    5050  const id = offerIdFrom(off);
    5151
    52   // The kid sees the offer and it needs its accept.
     52  // The kid sees the offer needing its accept; the candidate already agreed
     53  // (the Offer is the candidate's accept), so it just waits.
    5354  const kidQ = G.offersCollection(`${KID}/queues/offers`, 'kid', KID).orderedItems;
    5455  assert.equal(kidQ.length, 1);
    5556  assert.equal(kidQ[0]['shaer:needsMyAccept'], true);
    56   assert.equal(kidQ[0]['shaer:iAmCandidate'], false);
     57  assert.deepEqual(kidQ[0]['shaer:acceptedBy'], [ME]);       // candidate accepted via the offer
     58  const parentQ0 = G.offersCollection(`${ME}/queues/offers`, 'parent', ME).orderedItems;
     59  assert.equal(parentQ0[0]['shaer:needsMyAccept'], false);   // candidate already agreed
    5760
    58   // Not committed on a lone candidate — the ward has not accepted.
     61  // Not committed until the ward agrees.
    5962  assert.deepEqual(G.listGuardians('kid'), []);
    6063
    61   // The kid accepts (C2S from the kid's own Klonkt). Not committed yet: the
    62   // candidate must still agree to serve (§3.1.2).
    63   await G.handleGuardianshipOutbox(kid, { type: 'Accept', object: id });
    64   assert.deepEqual(G.listGuardians('kid'), []);
    65   const parentQ = G.offersCollection(`${ME}/queues/offers`, 'parent', ME).orderedItems;
    66   assert.equal(parentQ[0]['shaer:iAmCandidate'], true);
    67   assert.equal(parentQ[0]['shaer:needsMyAccept'], true);   // candidate has not accepted
    68 
    69   // The candidate accepts → tally complete → commit everywhere.
    70   const done = await G.handleGuardianshipOutbox(parent, { type: 'Accept', object: id });
     64  // The kid accepts → free ward, no existing guardian to co-approve → commit.
     65  const done = await G.handleGuardianshipOutbox(kid, { type: 'Accept', object: id });
    7166  assert.equal(done.committed, true);
    7267  assert.deepEqual(G.listGuardians('kid').map((g) => g.other_uri), [ME]);
     
    9186  assert.deepEqual(parentQ['shaer:existingGuardians'], [ME]);
    9287
    93   // Kid accepts, then gran (candidate) accepts — still NOT committed, because
    94   // the existing guardian (parent) has not co-accepted (§3.1.2).
    95   await G.handleGuardianshipOutbox(kid, { type: 'Accept', object: id });
    96   const early = await G.handleGuardianshipOutbox(gran, { type: 'Accept', object: id });
     88  // The kid accepts, but now it IS a ward: NOT committed, because the existing
     89  // guardian (parent) has not co-accepted (§3.1.2). The candidate (gran) already
     90  // agreed via the offer, so no separate gran accept is needed.
     91  const early = await G.handleGuardianshipOutbox(kid, { type: 'Accept', object: id });
    9792  assert.equal(early.committed, false);
    9893  assert.equal(G.listGuardians('kid').length, 1, 'still just the first guardian');
Note: See TracChangeset for help on using the changeset viewer.