Changeset f3a2556 in Klonkt
- Timestamp:
- 08/08/2026 06:35:21 AM (5 weeks ago)
- Branches:
- main
- Children:
- 2bfe26c
- Parents:
- cf5073b
- git-author:
- Robin <roboburr@…> (08/08/2026 06:35:19 AM)
- git-committer:
- roboburr <roboburr@…> (08/08/2026 06:35:21 AM)
- Files:
-
- 4 edited
-
src/routes/guardian.js (modified) (5 diffs)
-
src/services/guardianship/index.js (modified) (1 diff)
-
src/services/guardianship/queues.js (modified) (3 diffs)
-
test/guardian-gate-panel.test.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/guardian.js
rcf5073b rf3a2556 123 123 embeds: wardEmbedSetting(w.other_uri), 124 124 playback: wardPlaybackSetting(w.other_uri), 125 guardians: wardGuardianStatuses(w.other_uri),125 guardians: Guardianship.queues.wardGuardianStatuses(w.other_uri), 126 126 // What THIS guardian proposed for this ward and how it stands (5.6): 127 127 // open, accepted, rejected, or expired when the window ran out and the … … 135 135 // de drempel (shaer-ahy.1). Losse knoppen lieten een guardian zelf 136 136 // uitzoeken wat er allemaal geldt; wat niet verstelbaar is stond nergens. 137 gates: wardGates(site.slug, w.other_uri),137 gates: Guardianship.queues.wardGates(site.slug, w.other_uri), 138 138 })), 139 139 offers: Guardianship.offersCollection(`${me}/queues/offers`, site.slug, me).orderedItems, … … 155 155 } 156 156 157 /** The guardians of a ward WE host, with availability (3.6.1: owner-only in158 * spirit; the co-guardians are among the owners of the relationship). Null159 * for a remote ward: its server tracks availability, not us. */160 function wardGuardianStatuses(wardUri) {161 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');162 if (!base || !String(wardUri || '').startsWith(`${base}/`)) return null;163 const slug = String(wardUri).trim().replace(/\/+$/, '').split('/').pop();164 try {165 const uris = Guardianship.listGuardians(slug).map((g) => ({ uri: g.other_uri, handle: g.other_handle }));166 const st = Object.fromEntries(167 Guardianship.availability.statusesFor(slug, uris.map((u) => u.uri), Date.now()).map((s) => [s.id, s]),168 );169 return uris.map((u) => ({170 uri: u.uri,171 handle: u.handle,172 availability: (st[u.uri] || {})['shaer:availability'] || 'active',173 awayUntil: (st[u.uri] || {})['shaer:awayUntil'] || null,174 lapse: (st[u.uri] || {})['shaer:lapse'] || null,175 }));176 } catch { return null; }177 }178 157 179 158 // ── The PWA page ───────────────────────────────────────────────────────── … … 578 557 return res.status(403).json({ error: 'not_my_ward' }); 579 558 } 580 const local = wardGuardianStatuses(uri);559 const local = Guardianship.queues.wardGuardianStatuses(uri); 581 560 if (local) return res.json({ local: true, guardians: local }); 582 561 const doc = await AP.fetchActor(uri).catch(() => null); … … 613 592 /** The playback gate of a ward we host (5.6): the heavier sibling. */ 614 593 function wardPlaybackSetting(uri) { return wardGateSetting(uri, 'external_playback'); } 615 /**616 * De gate-rijen van een ward voor het paneel.617 *618 * De standen komen uit onze eigen kolommen als we het kind hosten; bij een ward619 * elders weten we ze niet en blijft het NULL -- onbekend, niet uit. Het aantal620 * guardians idem: dat wordt op de server van die ward bijgehouden, en zonder dat621 * getal wordt er geen drempel verzonnen.622 */623 function wardGates(mySlug, wardUri) {624 const statuses = wardGuardianStatuses(wardUri);625 const wachtend = Guardianship.follows.listReviewsByDirection(mySlug, 'incoming')626 .filter((r) => r.ward_uri === wardUri).length;627 return Guardianship.gated.gateRows({628 // Uit de BESLUITEN, niet uit onze eigen kolom. Er zijn geen lokale accounts:629 // elke ward woont elders, dus wardEmbedSetting() gaf voor iedere ward null en630 // stond er in het paneel overal "onbekend". Wat een guardian wel heeft is de631 // uitslag van wat hij voorstelde.632 settings: Object.fromEntries(Guardianship.gated.GATE_CATALOGUE633 .filter((g) => g.available !== false && Guardianship.gated.featureColumn(g.feature))634 .map((g) => [g.feature, Guardianship.gated.knownSetting(mySlug, wardUri, g.feature)])),635 guardianCount: statuses ? statuses.length : null,636 proposals: Guardianship.gated.listSent(mySlug, wardUri).map((p) => ({637 feature: p.feature, value: !!p.value, status: Guardianship.gated.sentStatus(p, Date.now()),638 })),639 waiting: { 'shaer:follows': wachtend || undefined },640 });641 }642 594 643 595 function wardGateSetting(uri, column) { -
src/services/guardianship/index.js
rcf5073b rf3a2556 32 32 // §5.6 gated settings (decided by the guardians, enforced by the ward's server) 33 33 export * as gated from './gated.js'; 34 export * as queues from './queues.js'; 34 35 // 5.2.1: wie er op een hulpvraag af is en wanneer hij is afgesloten (shaer-lgo) 35 36 export * as help from './help.js'; -
src/services/guardianship/queues.js
rcf5073b rf3a2556 14 14 import * as outgoing from './outgoing.js'; 15 15 import * as follows from './follows.js'; 16 import * as gated from './gated.js'; 16 17 import * as handshake from './handshake.js'; 17 18 … … 94 95 export function wardsCollection(id, slug) { 95 96 const items = relations.listWards(slug) 96 .map((r) => ({ id: r.other_uri, 'shaer:handle': r.other_handle || undefined, since: r.created_at })); 97 .map((r) => ({ 98 id: r.other_uri, 99 'shaer:handle': r.other_handle || undefined, 100 since: r.created_at, 101 // Alles wat voor dit kind gated is, met soort, drempel en lopend voorstel 102 // (shaer-ahy.1). Zonder dit kon een app wel een ward TONEN maar niets over 103 // hem zeggen -- en dat is precies de helft van het antwoord op "wat mag 104 // dit kind". Dezelfde rijen als het PWA-paneel, uit dezelfde functie. 105 'shaer:gates': wardGates(slug, r.other_uri), 106 })); 97 107 return collection(id, items); 98 108 } … … 105 115 } 106 116 107 export default { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection }; 117 export default { offersCollection, followsCollection, outgoingFollowsCollection, wardsCollection, guardiansCollection, wardGates, wardGuardianStatuses }; 118 119 // ── Wat er voor een ward gated is (shaer-ahy.1) ───────────────────────── 120 // 121 // STOND IN routes/guardian.js, en daar kon alleen de PWA erbij. De Shaer-apps 122 // lezen dezelfde toestand via de wards-queue, en een tweede berekening naast 123 // deze zou vroeg of laat een ander antwoord geven op dezelfde vraag -- dat is 124 // hier geen schoonheidsfoutje maar twee guardians die een verschillend beeld 125 // van hetzelfde kind krijgen. Een plek dus, en beide schermen lezen eruit. 126 /** The guardians of a ward WE host, with availability (3.6.1: owner-only in 127 * spirit; the co-guardians are among the owners of the relationship). Null 128 * for a remote ward: its server tracks availability, not us. */ 129 export function wardGuardianStatuses(wardUri) { 130 const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''); 131 if (!base || !String(wardUri || '').startsWith(`${base}/`)) return null; 132 const slug = String(wardUri).trim().replace(/\/+$/, '').split('/').pop(); 133 try { 134 const uris = relations.listGuardians(slug).map((g) => ({ uri: g.other_uri, handle: g.other_handle })); 135 const st = Object.fromEntries( 136 availability.statusesFor(slug, uris.map((u) => u.uri), Date.now()).map((s) => [s.id, s]), 137 ); 138 return uris.map((u) => ({ 139 uri: u.uri, 140 handle: u.handle, 141 availability: (st[u.uri] || {})['shaer:availability'] || 'active', 142 awayUntil: (st[u.uri] || {})['shaer:awayUntil'] || null, 143 lapse: (st[u.uri] || {})['shaer:lapse'] || null, 144 })); 145 } catch { return null; } 146 } 147 /** 148 * De gate-rijen van een ward voor het paneel. 149 * 150 * De standen komen uit onze eigen kolommen als we het kind hosten; bij een ward 151 * elders weten we ze niet en blijft het NULL -- onbekend, niet uit. Het aantal 152 * guardians idem: dat wordt op de server van die ward bijgehouden, en zonder dat 153 * getal wordt er geen drempel verzonnen. 154 */ 155 export function wardGates(mySlug, wardUri) { 156 const statuses = wardGuardianStatuses(wardUri); 157 const wachtend = follows.listReviewsByDirection(mySlug, 'incoming') 158 .filter((r) => r.ward_uri === wardUri).length; 159 return gated.gateRows({ 160 // Uit de BESLUITEN, niet uit onze eigen kolom. Er zijn geen lokale accounts: 161 // elke ward woont elders, dus wardEmbedSetting() gaf voor iedere ward null en 162 // stond er in het paneel overal "onbekend". Wat een guardian wel heeft is de 163 // uitslag van wat hij voorstelde. 164 settings: Object.fromEntries(gated.GATE_CATALOGUE 165 .filter((g) => g.available !== false && gated.featureColumn(g.feature)) 166 .map((g) => [g.feature, gated.knownSetting(mySlug, wardUri, g.feature)])), 167 guardianCount: statuses ? statuses.length : null, 168 proposals: gated.listSent(mySlug, wardUri).map((p) => ({ 169 feature: p.feature, value: !!p.value, status: gated.sentStatus(p, Date.now()), 170 })), 171 waiting: { 'shaer:follows': wachtend || undefined }, 172 }); 173 } -
test/guardian-gate-panel.test.js
rcf5073b rf3a2556 145 145 } 146 146 }); 147 148 // ── Dezelfde poorten voor het paneel en voor de apps (shaer-ahy.1) ─────── 149 150 test('de wards-queue draagt de poorten mee', async () => { 151 // Zonder dit kon een app een ward wel TONEN maar niets over hem zeggen. De 152 // apps lezen deze collectie; het PWA-paneel leest wardGates rechtstreeks. 153 const q = await import('../src/services/guardianship/queues.js'); 154 const rel = await import('../src/services/guardianship/relations.js'); 155 const dbm = await import('../src/config/database.js'); 156 dbm.initializeDatabase(); 157 dbm.default.prepare('INSERT OR IGNORE INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)') 158 .run('gu', 'gu', 'gu@t', 'x', 'god'); 159 dbm.default.prepare('INSERT OR IGNORE INTO sites (id, slug, title, owner_id) VALUES (?,?,?,?)').run('gs', 'oma', 'Oma', 'gu'); 160 rel.commitWardForGuardian('oma', 'https://elders/ap/users/kind', { handle: '@kind' }); 161 162 const coll = q.wardsCollection('https://x/queues/wards', 'oma'); 163 const ward = coll.orderedItems.find((w) => w.id === 'https://elders/ap/users/kind'); 164 assert.ok(ward, 'de ward staat in de collectie'); 165 assert.ok(Array.isArray(ward['shaer:gates']), 'en draagt zijn poorten'); 166 assert.equal(ward['shaer:gates'].length, gated.GATE_CATALOGUE.length); 167 }); 168 169 test('de app krijgt PRECIES dezelfde rijen als het paneel', async () => { 170 // Twee berekeningen naast elkaar geven vroeg of laat een ander antwoord op 171 // dezelfde vraag, en dat is hier geen schoonheidsfoutje: dan krijgen twee 172 // guardians een verschillend beeld van hetzelfde kind. 173 const q = await import('../src/services/guardianship/queues.js'); 174 const coll = q.wardsCollection('https://x/queues/wards', 'oma'); 175 const uitQueue = coll.orderedItems.find((w) => w.id === 'https://elders/ap/users/kind')['shaer:gates']; 176 const uitPaneel = q.wardGates('oma', 'https://elders/ap/users/kind'); 177 assert.deepEqual(uitQueue, uitPaneel); 178 });
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)