Changeset 30d0e2c in Klonkt for test/guardianship.test.js


Ignore:
Timestamp:
08/03/2026 06:38:16 AM (5 weeks ago)
Author:
Bart <bart@…>
Branches:
main
Children:
3d882bd
Parents:
69bd747
Message:

Een handshake blijft een week open, en faalt daarna onder zijn eigen naam

§4.2 leunt erop dat het uitstel begrensd is: als het venster sluit met de
controle nog onbeslist, faalt de beslissing dicht. Alleen had een
guardianship-offer in Klonkt helemaal geen venster, dus "uitgesteld" was
"voor altijd".

Nu een week. Lang genoeg dat niemand wordt opgejaagd — er moeten een ward, een
kandidaat en alle bestaande guardians antwoorden, en dat zijn mensen — en kort
genoeg dat een vergeten aanbod niet een maand in de wachtrij van een kind staat
alsof het nog een keuze is.

Twee eindtoestanden, want het zijn twee verschillende feiten:
'expired' (niemand heeft geantwoord; zegt niets over de kandidaat) en
'unverified' (iedereen heeft geantwoord, maar de kandidaat was nooit op te
halen). Geen van beide is 'void': de partijen mag niet verteld worden dat de
kandidaat geweigerd is, want dat is niet gebeurd — er heeft alleen nooit iemand
kunnen kijken.

Vervallen gebeurt bij het lezen, zoals een lapse dat ook doet: geen sweeper die
niemand draait. En het lezen van de wachtrij is meteen het moment waarop een
uitgestelde commit opnieuw wordt geprobeerd (§4.2 SHOULD) — nodig, want de
laatste Accept kan al binnen zijn en dan port niemand er ooit nog aan. Niet
awaited: een poll toont wat nu waar is.

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • test/guardianship.test.js

    r69bd747 r30d0e2c  
    187187  );
    188188  assert.deepEqual(G.listGuardians('zed'), []);
     189});
     190
     191test('an unreadable candidate defers the commit, and the window names that failure (§4.2)', async () => {
     192  const offers = await import('../src/services/guardianship/offers.js');
     193  const noor = site('s16', 'noor');
     194  const NOOR = A('noor');
     195  const MARA = 'https://elders.example/users/mara';   // remote; fetchActor returns null here
     196  const offerId = `${MARA}/offers/m1`;
     197
     198  // The Offer arrives and is STORED: unreachable is not malformed, and
     199  // refusing on a failed fetch would let any outage block an adoption.
     200  const taken = await G.handleGuardianshipInbox(noor, {
     201    id: offerId, type: 'Offer', actor: MARA, to: [NOOR],
     202    object: { type: 'Relationship', subject: NOOR, relationship: 'shaer:Guardian', object: MARA },
     203  });
     204  assert.equal(taken, true);
     205
     206  // Noor accepts. Free ward + candidate's own offer = the tally is complete,
     207  // so this WOULD commit — except the candidate cannot be read.
     208  const done = await G.handleGuardianshipOutbox(noor, { type: 'Accept', object: offerId });
     209  assert.equal(done.committed, false, 'not committed: nobody verified the candidate');
     210  assert.ok(!done.refused, 'and not refused either — that would blame a candidate nobody could look at');
     211  assert.deepEqual(G.listGuardians('noor'), []);
     212  assert.equal(offers.getOffer('noor', offerId).status, 'pending', 'deferred, not decided');
     213
     214  // A week later the §3.5 window closes and it fails closed — under its own
     215  // name. Not 'void': the parties must not be told the candidate was refused.
     216  const later = Date.now() + offers.OFFER_WINDOW_MS + 1000;
     217  offers.expireIfDue('noor', offerId, later);
     218  assert.equal(offers.getOffer('noor', offerId).status, 'unverified');
     219
     220  const q = G.offersCollection(`${NOOR}/queues/offers`, 'noor', NOOR).orderedItems;
     221  assert.deepEqual(q.filter((o) => o.id === offerId), [], 'and it stops looking like a live choice');
     222});
     223
     224test('a handshake nobody finished expires under a different name (§3.5)', async () => {
     225  const offers = await import('../src/services/guardianship/offers.js');
     226  const finn = site('s17', 'finn');
     227  const iris = site('s18', 'iris');
     228  const [FINN, IRIS] = [A('finn'), A('iris')];
     229
     230  const off = await G.handleGuardianshipOutbox(finn, {
     231    type: 'Offer', object: { type: 'Relationship', subject: IRIS, relationship: 'shaer:Guardian', object: FINN },
     232  });
     233  // Iris never answers. Reading the queue after the window settles it.
     234  G.offersCollection(`${IRIS}/queues/offers`, 'iris', IRIS);          // still open now
     235  assert.equal(offers.getOffer('iris', off.id).status, 'pending');
     236  offers.listForParty('iris', IRIS, Date.now() + offers.OFFER_WINDOW_MS + 1000);
     237  assert.equal(offers.getOffer('iris', off.id).status, 'expired',
     238    'nobody answered — that says nothing about the candidate, so it is not "unverified"');
    189239});
    190240
Note: See TracChangeset for help on using the changeset viewer.