-- §5 / §6: users, permission events, and audit log. -- -- The users table is the app-owned canonical account record. Per §6.1, -- role is one of owner / admin / contributor; anonymous is the absence -- of a row (or the absence of a session). The §6.2 app-wide write-mute -- lives here as `muted`, structurally distinct from the §15.6 per-RFC -- mute (on watches) and the §15.8 per-user mute (notification_user_mutes). -- -- Per §15, the per-user notification preferences are inlined for -- proximity. The watched-RFC-churn category has no column per §15.4 — -- it is permanently off and surfaces in settings as a disabled toggle. CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, gitea_id INTEGER UNIQUE NOT NULL, gitea_login TEXT UNIQUE NOT NULL, email TEXT, display_name TEXT NOT NULL, avatar_url TEXT, role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'contributor')), muted INTEGER NOT NULL DEFAULT 0, -- §6.2 app-wide write-mute email_personal_direct INTEGER NOT NULL DEFAULT 1, -- §15.4 default on email_watched_structural INTEGER NOT NULL DEFAULT 0, -- §15.4 default off email_admin_actionable INTEGER NOT NULL DEFAULT 1, -- §15.4 default on for admins/owners; ignored for contributors digest_cadence TEXT NOT NULL DEFAULT 'weekly' CHECK (digest_cadence IN ('off', 'weekly', 'daily')), -- §15.5 notification_quiet_hours_start TEXT, -- §15.8 ISO-8601 local time HH:MM notification_quiet_hours_end TEXT, notification_quiet_hours_timezone TEXT, -- IANA tz name created_at TEXT NOT NULL DEFAULT (datetime('now')), last_seen_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_users_role ON users (role); -- §6.5: permission-change audit. Append-only. Every mute, role grant, -- or capability override produces a row here. CREATE TABLE permission_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, subject_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, event_kind TEXT NOT NULL, -- e.g. role_changed, muted, restored details TEXT, -- JSON blob with before/after, reason, etc. created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_permission_events_subject ON permission_events (subject_user_id, created_at); -- §5: append-only action log. Every state transition, every graduation, -- every grant change. Includes the on-behalf-of trailer per §6.5 so the -- audit log and the Git log carry the same accountability. CREATE TABLE actions ( id INTEGER PRIMARY KEY AUTOINCREMENT, actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL, on_behalf_of TEXT NOT NULL, -- the gitea_login the bot acted on behalf of action_kind TEXT NOT NULL, -- propose_rfc, merge_proposal, graduate, etc. rfc_slug TEXT, branch_name TEXT, pr_number INTEGER, bot_commit_sha TEXT, details TEXT, -- JSON blob with kind-specific extras created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX idx_actions_rfc ON actions (rfc_slug, created_at); CREATE INDEX idx_actions_actor ON actions (actor_user_id, created_at);