Skip to main content

max / goingson

3.8 KB · 68 lines History Blame Raw
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 CREATE TABLE IF 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 CREATE INDEX IF NOT EXISTS idx_group_admin_queue_pending
67 ON group_admin_queue(user_id) WHERE done_at IS NULL;
68