Changeset c8e03c6 in Klonkt
- Timestamp:
- 07/29/2026 10:29:55 AM (6 weeks ago)
- Branches:
- main
- Children:
- 6d5ce0c
- Parents:
- 329873e
- Files:
-
- 3 edited
-
src/routes/guardian.js (modified) (1 diff)
-
src/services/guardianship/handshake.js (modified) (2 diffs)
-
test/gated-settings.test.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/guardian.js
r329873e rc8e03c6 516 516 // the proposal exists and a threshold of two can never be met. 517 517 if (r.state === 'open') { 518 const wardActor = AP.actorId(base, localWard.slug); 518 519 for (const g of Guardianship.listGuardians(localWard.slug).map((x) => x.other_uri)) { 519 520 if (g === me) continue; 520 AP.deliverToActor(site, g, { ...offer, to: [g] }).catch(() => { /* queued */ }); 521 // Signed by the ward, so the body must say the ward: anything else is 522 // a signer mismatch and the receiver answers 401 (as it should). 523 AP.deliverToActor( 524 db.prepare('SELECT * FROM sites WHERE slug = ?').get(localWard.slug), 525 g, 526 { ...offer, actor: wardActor, to: [g], 'shaer:proposer': me }, 527 ).catch(() => { /* queued */ }); 521 528 } 522 529 } -
src/services/guardianship/handshake.js
r329873e rc8e03c6 317 317 for (const g of relations.listGuardians(site.slug).map((x) => x.other_uri)) { 318 318 if (g === actor) continue; // the proposer already answered 319 // The forward goes out AS THE WARD, because the ward's key signs 320 // it. Keeping the proposer in `actor` made every receiver answer 321 // 401 signer mismatch, and rightly so: the body claimed one author 322 // and the signature proved another. §5.3 forwards a gated follow 323 // the same way. Who proposed it rides along separately, for the 324 // guardian's screen. 319 325 deps.deliverTo(site, g, { 320 id: offerId, type: 'Offer', actor, to: [g], object: activity.object, 326 id: offerId, type: 'Offer', actor: me, to: [g], object: activity.object, 327 'shaer:proposer': actor, 321 328 }).catch(() => { /* the delivery queue retries */ }); 322 329 } … … 333 340 gated.recordGatedReview(site.slug, { 334 341 id: offerId, wardUri: gs.ward, wardInbox: wardDoc && wardDoc.inbox, 335 proposer: actor, feature: gs.feature, value: gs.value, 342 // A forward is signed by the ward, so `actor` is the ward; the 343 // guardian who opened it travels in shaer:proposer. 344 proposer: (typeof activity['shaer:proposer'] === 'string' ? activity['shaer:proposer'] : actor), 345 feature: gs.feature, value: gs.value, 336 346 }); 337 347 notify(site.slug, { kind: 'gated_review', feature: gs.feature, value: gs.value, ward: gs.ward }); -
test/gated-settings.test.js
r329873e rc8e03c6 116 116 117 117 // The forward: B and C are told, A is not asked twice. 118 const told = sent.filter((x) => x.act.object && x.act.object['shaer:feature']).map((x) => x.to).sort(); 119 assert.deepEqual(told, [B, C].sort(), 'both other guardians must receive the proposal'); 118 const fwd = sent.filter((x) => x.act.object && x.act.object['shaer:feature']); 119 assert.deepEqual(fwd.map((x) => x.to).sort(), [B, C].sort(), 'both other guardians must receive the proposal'); 120 // And it must go out AS THE WARD, because the ward's key signs it. Sending 121 // it with the proposer still in `actor` is a signer mismatch: every receiver 122 // answers 401 and the proposal silently never arrives. Live proof, from 123 // beta's log: "guardianship Offer got 401 from boiert.eu/.../inbox". The 124 // first version of this test checked THAT a forward happened and not on 125 // whose behalf, so it passed while nothing worked. 126 for (const x of fwd) { 127 assert.equal(x.act.actor, WARD, 'the forward is signed by the ward, so it must say the ward'); 128 assert.equal(x.act['shaer:proposer'], A, 'and it carries who actually proposed it'); 129 } 120 130 121 131 // B receives its copy on its own server and can answer it. … … 123 133 database.prepare("INSERT OR IGNORE INTO ap_guardianships (slug, role, other_uri, status, offer_id) VALUES ('gb','guardian',?, 'accepted','o')").run(WARD); 124 134 const gbSite = database.prepare('SELECT * FROM sites WHERE slug = ?').get('gb'); 125 assert.equal(await G.handleGuardianshipInbox(gbSite, { ...offer, actor: A, to: ['https://test.example/ap/users/gb'] }), true); 135 // Exactly the shape the ward sends: actor = the ward, proposer alongside. 136 assert.equal(await G.handleGuardianshipInbox(gbSite, { 137 ...offer, actor: WARD, 'shaer:proposer': A, to: ['https://test.example/ap/users/gb'], 138 }), true); 126 139 const review = G.gated.listGatedReviews('gb')[0]; 127 140 assert.ok(review, 'the guardian stores a copy it can answer'); 141 assert.equal(review.proposer, A, 'the screen names who proposed it, not the ward that relayed it'); 128 142 assert.equal(review.feature, 'shaer:externalEmbeds'); 129 143 assert.equal(review.ward_uri, WARD);
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)