| [6b5d7da] | 1 | /**
|
|---|
| [780a7c6] | 2 | * Guardianship (FEP-633c) — the COMMITTED ward ↔ guardian relations
|
|---|
| 3 | * (ap_guardianships). Pending offers live in offers.js; a row here means the
|
|---|
| 4 | * handshake committed (§3.1.4). Every row is one relation seen from a LOCAL
|
|---|
| 5 | * site: role 'guardian' = the site guards other_uri; role 'ward' = other_uri
|
|---|
| 6 | * guards the site.
|
|---|
| [6b5d7da] | 7 | */
|
|---|
| 8 | import db from '../../config/database.js';
|
|---|
| 9 |
|
|---|
| 10 | let _s = null;
|
|---|
| 11 | function stmts() {
|
|---|
| 12 | if (!_s) {
|
|---|
| 13 | _s = {
|
|---|
| [780a7c6] | 14 | commit: db.prepare(`INSERT INTO ap_guardianships (slug, role, other_uri, other_handle, status, offer_id, created_at)
|
|---|
| 15 | VALUES (?,?,?,?, 'accepted', ?, CURRENT_TIMESTAMP)
|
|---|
| 16 | ON CONFLICT(slug, role, other_uri) DO UPDATE SET status='accepted', offer_id=excluded.offer_id`),
|
|---|
| [6b5d7da] | 17 | del: db.prepare('DELETE FROM ap_guardianships WHERE slug=? AND role=? AND other_uri=?'),
|
|---|
| [780a7c6] | 18 | bySlugRole: db.prepare("SELECT * FROM ap_guardianships WHERE slug=? AND role=? AND status='accepted' ORDER BY created_at DESC"),
|
|---|
| [6b5d7da] | 19 | one: db.prepare('SELECT * FROM ap_guardianships WHERE slug=? AND role=? AND other_uri=?'),
|
|---|
| 20 | };
|
|---|
| 21 | }
|
|---|
| 22 | return _s;
|
|---|
| 23 | }
|
|---|
| 24 |
|
|---|
| 25 | // ── Reads ────────────────────────────────────────────────────────────────
|
|---|
| 26 |
|
|---|
| 27 | /** Accepted guardian URIs of a local ward (feeds shaer:guardians). */
|
|---|
| [780a7c6] | 28 | export function listGuardians(slug) { return stmts().bySlugRole.all(slug, 'ward'); }
|
|---|
| [6b5d7da] | 29 |
|
|---|
| [780a7c6] | 30 | /** Accepted wards of a local guardian (the wards queue). */
|
|---|
| 31 | export function listWards(slug) { return stmts().bySlugRole.all(slug, 'guardian'); }
|
|---|
| [6b5d7da] | 32 |
|
|---|
| [780a7c6] | 33 | /** A site is a guardian once it stands in any accepted guardian relation. */
|
|---|
| 34 | export function isGuardian(slug) { return listWards(slug).length > 0; }
|
|---|
| [6b5d7da] | 35 |
|
|---|
| 36 | export function getRelation(slug, role, otherUri) { return stmts().one.get(slug, role, otherUri); }
|
|---|
| 37 |
|
|---|
| [780a7c6] | 38 | // ── Writes (only the handshake commit lands here) ────────────────────────
|
|---|
| [6b5d7da] | 39 |
|
|---|
| [780a7c6] | 40 | /** The local ward gains a guardian (commit, §3.1.4). */
|
|---|
| 41 | export function commitGuardianForWard(wardSlug, guardianUri, { handle = null, offerId = null } = {}) {
|
|---|
| 42 | stmts().commit.run(wardSlug, 'ward', guardianUri, handle, offerId);
|
|---|
| 43 | return stmts().one.get(wardSlug, 'ward', guardianUri);
|
|---|
| [6b5d7da] | 44 | }
|
|---|
| 45 |
|
|---|
| [780a7c6] | 46 | /** The local guardian gains a ward (commit, §3.1.4). */
|
|---|
| 47 | export function commitWardForGuardian(guardianSlug, wardUri, { handle = null, offerId = null } = {}) {
|
|---|
| 48 | stmts().commit.run(guardianSlug, 'guardian', wardUri, handle, offerId);
|
|---|
| 49 | return stmts().one.get(guardianSlug, 'guardian', wardUri);
|
|---|
| [6b5d7da] | 50 | }
|
|---|
| 51 |
|
|---|
| [780a7c6] | 52 | /** End a relation locally (Undo, §3.2 — federation of the Undo is Fase 4). */
|
|---|
| [6b5d7da] | 53 | export function removeRelation(slug, role, otherUri) {
|
|---|
| 54 | stmts().del.run(slug, role, otherUri);
|
|---|
| 55 | return { ok: true };
|
|---|
| 56 | }
|
|---|
| 57 |
|
|---|
| 58 | // ── Actor document (FEP-633c §2) ─────────────────────────────────────────
|
|---|
| 59 |
|
|---|
| 60 | /**
|
|---|
| [780a7c6] | 61 | * Guardianship props for a local actor doc. `id` is the actor URI.
|
|---|
| 62 | * - shaer:guardians: accepted guardians of this ward (omitted when none, §2.1)
|
|---|
| [6b5d7da] | 63 | * - shaer:isGuardian: true once the site guards anyone
|
|---|
| [780a7c6] | 64 | * - shaer:queues: the owner-only dashboard collections
|
|---|
| 65 | *
|
|---|
| 66 | * §1 mutual exclusion: a ward (has guardians) is never a guardian, so
|
|---|
| 67 | * shaer:isGuardian is suppressed if guardians exist; the offer path already
|
|---|
| 68 | * bars a ward from offering.
|
|---|
| [6b5d7da] | 69 | */
|
|---|
| 70 | export function actorProps(id, slug) {
|
|---|
| 71 | const props = {
|
|---|
| 72 | 'shaer:queues': {
|
|---|
| 73 | offers: `${id}/queues/offers`,
|
|---|
| 74 | follows: `${id}/queues/follows`,
|
|---|
| 75 | wards: `${id}/queues/wards`,
|
|---|
| 76 | },
|
|---|
| 77 | };
|
|---|
| 78 | const guardians = listGuardians(slug).map((r) => r.other_uri);
|
|---|
| [780a7c6] | 79 | if (guardians.length) {
|
|---|
| 80 | props['shaer:guardians'] = guardians; // a ward
|
|---|
| 81 | } else if (isGuardian(slug)) {
|
|---|
| 82 | props['shaer:isGuardian'] = true; // a guardian (never both, §1)
|
|---|
| 83 | }
|
|---|
| [6b5d7da] | 84 | return props;
|
|---|
| 85 | }
|
|---|
| 86 |
|
|---|
| 87 | export default {
|
|---|
| [780a7c6] | 88 | listGuardians, listWards, isGuardian, getRelation,
|
|---|
| 89 | commitGuardianForWard, commitWardForGuardian, removeRelation, actorProps,
|
|---|
| [6b5d7da] | 90 | };
|
|---|