CREATE TABLE pages ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), space_id uuid NOT NULL REFERENCES spaces(id) ON DELETE CASCADE, slug text NOT NULL, title text NOT NULL, body_md text NOT NULL DEFAULT '', body_html text, parent_id uuid, -- Same-space parent enforcement (mirrors tasks.project_id pattern) FOREIGN KEY (parent_id, space_id) REFERENCES pages(id, space_id) ON DELETE SET NULL (parent_id), embedding vector(1024), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (space_id, slug), -- Composite key target for parent_id self-reference above + any future FK UNIQUE (id, space_id) ); CREATE TABLE page_revisions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), page_id uuid NOT NULL REFERENCES pages(id) ON DELETE CASCADE, body_md text NOT NULL, edited_by text, created_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE refs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), space_id uuid NOT NULL REFERENCES spaces(id) ON DELETE CASCADE, kind text NOT NULL CHECK (kind IN ('url','video','pdf','image','file')), source_url text, title text, description text, summary text, body_text text, blob_path text, thumbnail text, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, embedding vector(1024), status text NOT NULL DEFAULT 'ingested' CHECK (status IN ('ingested','indexed','enriched')), source_kind text, external_id text, captured_at timestamptz NOT NULL DEFAULT now(), created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX idx_pages_space ON pages(space_id); CREATE INDEX idx_pages_parent ON pages(parent_id); CREATE INDEX idx_pages_fts ON pages USING GIN (to_tsvector('english', title || ' ' || body_md)); CREATE INDEX idx_pages_embed ON pages USING hnsw (embedding vector_cosine_ops); CREATE INDEX idx_refs_space ON refs(space_id); CREATE INDEX idx_refs_kind ON refs(kind); -- Per-space uniqueness on external dedup (mirrors tenancy-first pattern of 001). -- upsertByExternal MUST pass space_id; cross-space same-external_id creates distinct rows. CREATE UNIQUE INDEX idx_refs_external_unique ON refs(space_id, source_kind, external_id) WHERE source_kind IS NOT NULL AND external_id IS NOT NULL; CREATE INDEX idx_refs_fts ON refs USING GIN ( to_tsvector('english', coalesce(title,'') || ' ' || coalesce(summary,'') || ' ' || coalesce(body_text,'')) ); CREATE INDEX idx_refs_embed ON refs USING hnsw (embedding vector_cosine_ops);