A lexicon-driven AppView for ATProto.
2.9 kB
59 lines
1-- Trigger-keyed scripts subsystem.
2--
3-- Each row's `id` IS the trigger string the dispatcher resolves on:
4-- record.index:<nsid> — wildcard for any record event
5-- record.create:<nsid> — specifically a create event (cascades to wildcard)
6-- record.update:<nsid> — specifically an update event
7-- record.delete:<nsid> — specifically a delete event
8-- xrpc.query:<nsid> — XRPC query handler
9-- xrpc.procedure:<nsid> — XRPC procedure handler
10-- labeler.apply:<nsid> — label arrives whose subject is at://<did>/<nsid>/<rkey>
11-- labeler.apply:_actor — label arrives whose subject is a bare DID
12--
13-- Cascade rule (record events ONLY): the dispatcher tries
14-- `record.<action>:<nsid>` first, falls back to `record.index:<nsid>` if no
15-- specific row exists. No cascade for XRPC or labeler triggers — those
16-- resolve directly. Operators express per-action surgical control by
17-- creating action-specific rows; the wildcard `record.index:<nsid>` covers
18-- "one body for everything" with branching on `event.action`.
19CREATE TABLE scripts (
20 id TEXT PRIMARY KEY,
21 body TEXT NOT NULL,
22 description TEXT,
23 script_type TEXT NOT NULL DEFAULT 'lua',
24 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
25 updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
26);
27
28-- Permanently-failed runs from the firehose-driven runners (record / label
29-- events). XRPC scripts fail-closed and never land here.
30CREATE TABLE dead_letter_scripts (
31 id BIGSERIAL PRIMARY KEY,
32 script_ref TEXT NOT NULL, -- = the trigger id whose script failed
33 host_kind TEXT NOT NULL, -- 'record' | 'label'
34 host_id TEXT NOT NULL, -- e.g. '<nsid>:<action>' for record, '<labeler-did>' for label
35 payload JSONB NOT NULL, -- event payload for re-run
36 error TEXT NOT NULL,
37 attempts INT NOT NULL DEFAULT 0,
38 created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
39 resolved_at TIMESTAMPTZ
40);
41
42CREATE INDEX idx_dead_letter_scripts_host
43 ON dead_letter_scripts (host_kind, host_id, created_at DESC);
44CREATE INDEX idx_dead_letter_scripts_resolved_at
45 ON dead_letter_scripts (resolved_at);
46
47-- Permissions: management for users who can manage lexicons; read for those who can read.
48-- granted_at lost its DEFAULT in 20260318 (type change to TEXT), so we must supply it.
49INSERT INTO user_permissions (user_id, permission, granted_at)
50SELECT user_id, 'scripts:manage', granted_at
51 FROM user_permissions
52 WHERE permission = 'lexicons:create'
53ON CONFLICT (user_id, permission) DO NOTHING;
54
55INSERT INTO user_permissions (user_id, permission, granted_at)
56SELECT user_id, 'scripts:read', granted_at
57 FROM user_permissions
58 WHERE permission = 'lexicons:read'
59ON CONFLICT (user_id, permission) DO NOTHING;