-- Phase 4 (v1.2 Hierarchies): group-owned scripts. -- -- Until now every script was owned by exactly one app (`app_id NOT NULL`, -- ON DELETE RESTRICT). Phase 4 lets a script be owned by a GROUP instead, so -- it is shared by every descendant app — resolved by name, nearest-owner -- wins (CoW: an app's own script of the same name shadows the inherited one), -- exactly like `vars`/`secrets` (0048/0049). -- -- Phase 4-LITE scope: group-owned ENDPOINT scripts only. Group modules and -- the origin-aware (lexical) import resolver are deferred to Phase 4b, so a -- group-owned script must be self-contained (enforced in the service layer). -- -- Reshape (mirrors vars/secrets, but RESTRICT not CASCADE — code is not data, -- a group can't be deleted out from under the scripts it owns, matching the -- existing app_id RESTRICT and the §5.6 delete=RESTRICT rule): -- * `group_id` — nullable FK→groups, ON DELETE RESTRICT. -- * `app_id` — made nullable (was NOT NULL); the exactly-one CHECK now -- enforces "owned by an app XOR a group". -- * the per-app unique name index becomes two partial indexes, one per owner. ALTER TABLE scripts ADD COLUMN group_id UUID REFERENCES groups(id) ON DELETE RESTRICT; ALTER TABLE scripts ALTER COLUMN app_id DROP NOT NULL; ALTER TABLE scripts ADD CONSTRAINT scripts_owner_exactly_one CHECK ((group_id IS NULL) <> (app_id IS NULL)); -- Per-owner, case-insensitive name uniqueness. Partial so each owner column -- only constrains its own rows; existing app rows (group_id NULL) keep the -- exact same (app_id, LOWER(name)) uniqueness they had under the old index. DROP INDEX scripts_name_uidx; CREATE UNIQUE INDEX scripts_app_name_uidx ON scripts (app_id, LOWER(name)) WHERE app_id IS NOT NULL; CREATE UNIQUE INDEX scripts_group_name_uidx ON scripts (group_id, LOWER(name)) WHERE group_id IS NOT NULL; CREATE INDEX scripts_group_id_idx ON scripts (group_id) WHERE group_id IS NOT NULL;