Changeset 88d7c8f in Klonkt for src/services/guardianship
- Timestamp:
- 07/29/2026 07:15:16 AM (6 weeks ago)
- Branches:
- main
- Children:
- e27b8db
- Parents:
- 439f095
- Location:
- src/services/guardianship
- Files:
-
- 2 edited
-
gated.js (modified) (2 diffs)
-
handshake.js (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
src/services/guardianship/gated.js
r439f095 r88d7c8f 145 145 } 146 146 147 // ── The guardian-side copy (the missing leg of §5.6) ────────────── 148 // A proposal addressed to the ward's server reaches only the proposer and the 149 // ward. The other guardians never learn it exists, so a threshold of two can 150 // never be met and every proposal expires unanswered. The ward's server 151 // therefore FORWARDS it, exactly as it forwards a gated follow (§5.3): each 152 // guardian stores a copy it can answer, and the answer travels back to the 153 // ward, which tallies. 154 155 let _rs = null; 156 function rstmts() { 157 if (!_rs) { 158 _rs = { 159 ins: db.prepare(`INSERT INTO ap_gated_reviews (id, guardian_slug, ward_uri, ward_inbox, proposer, feature, value) 160 VALUES (?,?,?,?,?,?,?) 161 ON CONFLICT(guardian_slug, id) DO UPDATE SET value = excluded.value, ward_inbox = excluded.ward_inbox`), 162 get: db.prepare('SELECT * FROM ap_gated_reviews WHERE guardian_slug = ? AND id = ?'), 163 bySlug: db.prepare('SELECT * FROM ap_gated_reviews WHERE guardian_slug = ? ORDER BY created_at DESC'), 164 del: db.prepare('DELETE FROM ap_gated_reviews WHERE guardian_slug = ? AND id = ?'), 165 delAll: db.prepare('DELETE FROM ap_gated_reviews WHERE id = ?'), 166 }; 167 } 168 return _rs; 169 } 170 171 export function recordGatedReview(guardianSlug, r) { 172 rstmts().ins.run(r.id, guardianSlug, r.wardUri, r.wardInbox || null, r.proposer || null, r.feature, r.value ? 1 : 0); 173 return rstmts().get.get(guardianSlug, r.id); 174 } 175 export function getGatedReview(guardianSlug, id) { return rstmts().get.get(guardianSlug, id); } 176 export function listGatedReviews(guardianSlug) { return rstmts().bySlug.all(guardianSlug); } 177 export function removeGatedReview(guardianSlug, id) { rstmts().del.run(guardianSlug, id); } 178 /** Drop every guardian's copy once the decision has settled or lapsed. */ 179 export function clearGatedReviews(id) { rstmts().delAll.run(id); } 180 147 181 export function rememberGatedOffer(offerId, slug, feature, value) { 148 182 try { … … 160 194 tallyGatedSetting, thresholdFor, featureColumn, recordGatedVote, gatedProgress, GATED_WINDOW_MS, 161 195 parseGatedSetting, buildGatedOffer, rememberGatedOffer, recallGatedOffer, 196 recordGatedReview, getGatedReview, listGatedReviews, removeGatedReview, clearGatedReviews, 162 197 }; -
src/services/guardianship/handshake.js
r439f095 r88d7c8f 302 302 const gs = gated.parseGatedSetting(activity.object); 303 303 if (gs) { 304 if (gs.ward !== me) return false; // not our ward 305 gated.rememberGatedOffer(idOf(activity), site.slug, gs.feature, gs.value); 306 // The proposer's Offer carries its own agreement (§3.1's one-step clause). 307 const r = gated.recordGatedVote(site.slug, gs.feature, actor, gs.value); 308 notify(site.slug, { kind: 'gated_setting', feature: gs.feature, value: gs.value, state: r.state }); 309 return true; 304 const offerId = idOf(activity); 305 // ── I am the WARD: record, tally, and forward to the other guardians. 306 if (gs.ward === me) { 307 gated.rememberGatedOffer(offerId, site.slug, gs.feature, gs.value); 308 // The proposer's Offer carries its own agreement (§3.1's one-step clause). 309 const r = gated.recordGatedVote(site.slug, gs.feature, actor, gs.value); 310 // The forward is the leg that was missing. A proposal addressed to the 311 // ward's server reaches only the proposer and the ward; the other 312 // guardians never learn it exists, so a threshold of two can never be 313 // met and every proposal expires unanswered. The ward's server is the 314 // one that knows the authoritative guardian list, which is exactly why 315 // §5.3 forwards a gated follow from here too. 316 if (r.state === 'open') { 317 for (const g of relations.listGuardians(site.slug).map((x) => x.other_uri)) { 318 if (g === actor) continue; // the proposer already answered 319 deps.deliverTo(site, g, { 320 id: offerId, type: 'Offer', actor, to: [g], object: activity.object, 321 }).catch(() => { /* the delivery queue retries */ }); 322 } 323 } else { 324 gated.clearGatedReviews(offerId); // settled at once: nothing left to ask 325 } 326 notify(site.slug, { kind: 'gated_setting', feature: gs.feature, value: gs.value, state: r.state }); 327 return true; 328 } 329 // ── I am one of the GUARDIANS: the forwarded copy. Store it so this 330 // guardian can answer; the answer goes back to the ward, which tallies. 331 if (relations.getRelation(site.slug, 'guardian', gs.ward)) { 332 const wardDoc = await deps.fetchActor(gs.ward).catch(() => null); 333 gated.recordGatedReview(site.slug, { 334 id: offerId, wardUri: gs.ward, wardInbox: wardDoc && wardDoc.inbox, 335 proposer: actor, feature: gs.feature, value: gs.value, 336 }); 337 notify(site.slug, { kind: 'gated_review', feature: gs.feature, value: gs.value, ward: gs.ward }); 338 return true; 339 } 340 return false; // not our ward, and not a ward we guard 310 341 } 311 342 // §3.6.3: a co-guardian proposes releasing a dormant guardian of THIS
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)