CREATE TABLE resources ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), space_id uuid NOT NULL REFERENCES spaces(id) ON DELETE CASCADE, slug text NOT NULL, name text NOT NULL, runtime_type text NOT NULL CHECK (runtime_type IN ('lxc','vm','docker','bare-metal')), host text, url text, version text, status text NOT NULL DEFAULT 'unknown' CHECK (status IN ('running','stopped','down','unknown')), monitoring jsonb NOT NULL DEFAULT '{}'::jsonb, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, last_check timestamptz, maintenance_until timestamptz, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE (space_id, slug), -- Composite key target for child tables enforcing same-space references UNIQUE (id, space_id) ); -- Both endpoints of a dependency must live in the same space. CREATE TABLE resource_dependencies ( resource_id uuid NOT NULL, depends_on uuid NOT NULL, space_id uuid NOT NULL, kind text, PRIMARY KEY (resource_id, depends_on), CHECK (resource_id <> depends_on), FOREIGN KEY (resource_id, space_id) REFERENCES resources(id, space_id) ON DELETE CASCADE, FOREIGN KEY (depends_on, space_id) REFERENCES resources(id, space_id) ON DELETE CASCADE ); -- Credentials inherit the resource's space; cross-tenant assignment impossible at DB layer. CREATE TABLE resource_credentials ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), resource_id uuid NOT NULL, space_id uuid NOT NULL, label text NOT NULL, vault_path text NOT NULL, kind text, notes text, created_at timestamptz NOT NULL DEFAULT now(), FOREIGN KEY (resource_id, space_id) REFERENCES resources(id, space_id) ON DELETE CASCADE ); -- source_docs: resource_id is NOT NULL (anchors tenancy) and not in repo update field list, -- so a source doc cannot be moved to a different resource. Tenancy inherited transitively. CREATE TABLE source_docs ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), resource_id uuid NOT NULL REFERENCES resources(id) ON DELETE CASCADE, name text NOT NULL, upstream_url text NOT NULL, version text, format text, sync_source text, local_path text, body_text text, embedding vector(1024), last_synced timestamptz, metadata jsonb NOT NULL DEFAULT '{}'::jsonb, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX idx_resources_space ON resources(space_id); CREATE INDEX idx_resource_deps_space ON resource_dependencies(space_id); CREATE INDEX idx_resource_creds_resource ON resource_credentials(resource_id, space_id); CREATE INDEX idx_source_docs_resource ON source_docs(resource_id); CREATE INDEX idx_source_docs_fts ON source_docs USING GIN (to_tsvector('english', coalesce(body_text,''))); CREATE INDEX idx_source_docs_embed ON source_docs USING hnsw (embedding vector_cosine_ops);