max / goingson
| 1 | -- A queue for the group-admin writes, so a described screen can offer them. |
| 2 | -- |
| 3 | -- WHAT LED HERE, the same road the outbox took. `create_group`, `add_member` |
| 4 | -- and `remove_member` each open a conversation with a server. A described route |
| 5 | -- handler is synchronous, by `quasi_router`'s Decision 6, which exists so egui |
| 6 | -- and a terminal do not need a runtime. So Settings > Sharing could show the |
| 7 | -- groups and could not change them: goingson `7f36900b`, and the reads were |
| 8 | -- only unblocked at all by synckit 0.9.0 writing the directory down. |
| 9 | -- |
| 10 | -- Queueing is a local write, so it can be described. This is migration 069's |
| 11 | -- argument applied a second time, and Max's ruling behind that one ("have |
| 12 | -- GoingsOn use an outbox model explicitly", task a3c76a24) is what makes it a |
| 13 | -- pattern rather than a workaround repeated. |
| 14 | -- |
| 15 | -- WHY A QUEUE IS BETTER HERE TOO, and it is not the same reason as the mail |
| 16 | -- one. There is no send-later value in creating a group. What there is: |
| 17 | -- |
| 18 | -- - an admin write survives being offline instead of failing at the instant |
| 19 | -- somebody pressed the button, which matters more for these than for mail |
| 20 | -- because they are rare and deliberate and nobody retries them by habit; |
| 21 | -- - a failure is a row with a reason on it, sitting where the person who |
| 22 | -- caused it will look, rather than a toast that has already gone; |
| 23 | -- - `add_member` needs the master key loaded to seal the group key. A queue |
| 24 | -- turns "you cannot do this right now" into "this will happen once you |
| 25 | -- unlock", which is the honest behaviour and not one a button could offer. |
| 26 | -- |
| 27 | -- A SEPARATE TABLE, unlike the outbox. A queued message is a draft that has |
| 28 | -- been committed to send, so it stays in `emails` and the outbox is a query. |
| 29 | -- These have no such home: there is no local `groups` table this app owns, and |
| 30 | -- `sync_groups` is synckit's copy of the server's answer, which this must not |
| 31 | -- write into. So the queue is its own store and the directory stays a mirror. |
| 32 | -- |
| 33 | -- LOCAL-ONLY, and deliberately absent from `syncstore::manifest`. An intention |
| 34 | -- to add somebody to a group is this device's, and replicating it would have a |
| 35 | -- second device perform the same admin write again. The server is the authority |
| 36 | -- on membership; this table is only ever a list of what has not reached it yet. |
| 37 | NOT EXISTS group_admin_queue ( |
| 38 | id TEXT PRIMARY KEY NOT NULL, |
| 39 | user_id TEXT NOT NULL, |
| 40 | -- 'create_group' | 'add_member' | 'remove_member'. Not a CHECK constraint: |
| 41 | -- a kind this build does not know is held rather than refused, the same way |
| 42 | -- an unknown table is on the sync side, so a downgrade does not destroy a |
| 43 | -- queued intention it merely cannot perform. |
| 44 | kind TEXT NOT NULL, |
| 45 | -- The target group, NULL for 'create_group' which is what makes one. |
| 46 | group_id TEXT, |
| 47 | -- What the kind needs. `create_group` reads `name`; `add_member` reads |
| 48 | -- `email` and `pubkey`; `remove_member` reads `member_user_id`. Columns |
| 49 | -- rather than a JSON blob, because there are three kinds and eleven fields |
| 50 | -- between them would be worse than four nullable ones. |
| 51 | name TEXT, |
| 52 | email TEXT, |
| 53 | pubkey TEXT, |
| 54 | member_user_id TEXT, |
| 55 | queued_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')), |
| 56 | -- Counted rather than capped: the drainer backs off on this, and a row that |
| 57 | -- can never succeed sits with its reason on it rather than being deleted. |
| 58 | attempts INTEGER NOT NULL DEFAULT 0, |
| 59 | last_error TEXT, |
| 60 | -- Set when the server has accepted it. A done row is kept until the |
| 61 | -- directory refresh that proves it landed, then swept, so the screen can |
| 62 | -- say "created" for the moment between the two. |
| 63 | done_at TEXT |
| 64 | ); |
| 65 | |
| 66 | NOT EXISTS idx_group_admin_queue_pending |
| 67 | ON group_admin_queue(user_id) WHERE done_at IS NULL; |
| 68 |