-- migrate:no-foreign-keys -- -- §22 three-tier — S3. Admit a *global*-scope grant to the memberships table. -- -- §B.2's resolver folds four layers (global → project → collection → per-entry). -- Migration 029 created `memberships` with scope_type ∈ {project, collection} -- only; the global tier was left to S3. A global grant is how a "global RFC -- Contributor" (a contributor who may propose in every collection of every -- project, distinct from a deployment owner/admin) is represented — see -- docs/design/2026-06-05-three-tier-projects-collections.md §B.2/§B.3 and the -- C.1 "cleo" scenario. -- -- SQLite can't ALTER a CHECK constraint in place, so the table is rebuilt by the -- create-copy-drop-rename procedure (the 028/029 pattern). The global scope uses -- a stable sentinel scope_id of '*' (one global tier per deployment); the -- UNIQUE(scope_type, scope_id, user_id) then admits exactly one global grant per -- user, mirroring the project/collection rows. CREATE TABLE memberships__new ( id INTEGER PRIMARY KEY AUTOINCREMENT, scope_type TEXT NOT NULL CHECK (scope_type IN ('global', 'project', 'collection')), scope_id TEXT NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN ('owner', 'contributor')), granted_by INTEGER REFERENCES users(id) ON DELETE SET NULL, granted_at TEXT NOT NULL DEFAULT (datetime('now')), UNIQUE (scope_type, scope_id, user_id) ); INSERT INTO memberships__new (id, scope_type, scope_id, user_id, role, granted_by, granted_at) SELECT id, scope_type, scope_id, user_id, role, granted_by, granted_at FROM memberships; DROP TABLE memberships; ALTER TABLE memberships__new RENAME TO memberships; CREATE INDEX idx_memberships_user ON memberships(user_id); CREATE INDEX idx_memberships_scope ON memberships(scope_type, scope_id);