First commit of §7 multi-repo ownership. A 'project' is a repo-root that declaratively manages a slice of the shared group tree; each group node is owned by exactly one project (single-owner-per-node). This lands the registry + shared types and wires the inert 0047 `owner_project` seam — no behavior change yet (claim logic is the next commit). - migration 0066: `projects` table (UUID pk, unique slug, name, created_by → admin_users ON DELETE SET NULL); `groups.owner_project` gains its FK → projects(id) ON DELETE SET NULL (un-claim, never cascade-destroy a tree) + an index. - shared: `ProjectId` (id_type! macro) + `Project` struct; `Group` gains `owner_project: Option<ProjectId>`. - group_repo: GROUP_COLS + the ancestors recursive-CTE term now carry `owner_project`, so `ancestors()` surfaces ownership for the nearest-claimed fold; GroupRow + From updated. - schema snapshot re-blessed; /version schema assertion 65 → 66. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
42 lines
2.3 KiB
SQL
42 lines
2.3 KiB
SQL
-- §7 multi-repo ownership: the `projects` registry + wiring the inert
|
|
-- `owner_project` seam.
|
|
--
|
|
-- A "project" is a repo-root that declaratively MANAGES a slice of the shared
|
|
-- group tree (contains its manifest as something it applies, not merely
|
|
-- references). §7's model is SINGLE-OWNER-PER-NODE: each group node is owned by
|
|
-- exactly one project; the first `pic apply` that declares a `[project]` claims
|
|
-- the node, and a second repo applying to an owned node is refused unless it
|
|
-- passes a group-admin-gated `--takeover`. Ownership ⟂ RBAC — it records which
|
|
-- manifest is authoritative, not whether a principal may act.
|
|
--
|
|
-- Identity is a committed `[project] slug` (stable across clones); the server
|
|
-- assigns the UUID (server-internal). The `owner_project UUID` column already
|
|
-- exists on `groups` (0047, inert) — this migration creates the table it was
|
|
-- always meant to reference and wires the FK. Apps carry NO owner column: an
|
|
-- app inherits ownership from its NEAREST claimed ancestor group (the ancestor
|
|
-- walk is the isolation boundary), faithful to §7's "don't co-own a node —
|
|
-- split config downward" corollary.
|
|
|
|
CREATE TABLE projects (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
-- Instance-global identity, declared in the repo's committed root manifest
|
|
-- and frozen. First apply with a new slug registers the project.
|
|
slug TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
-- The admin who first registered it. SET NULL (not CASCADE) — losing the
|
|
-- creator's account must not orphan-delete the project or un-claim its tree.
|
|
created_by UUID REFERENCES admin_users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
-- Wire the inert 0047 seam. ON DELETE SET NULL un-claims the owned nodes (they
|
|
-- fall back to UI/API-owned) rather than cascading a destructive tree delete —
|
|
-- deleting a project must never destroy the groups/apps it happened to manage.
|
|
ALTER TABLE groups
|
|
ADD CONSTRAINT groups_owner_project_fk
|
|
FOREIGN KEY (owner_project) REFERENCES projects(id) ON DELETE SET NULL;
|
|
|
|
-- Ownership lookups: "what does project X own?" (pic projects ls) and the
|
|
-- LEFT JOIN behind pic groups ls' owner column.
|
|
CREATE INDEX groups_owner_project_idx ON groups (owner_project);
|