-- ========================================== -- Chat Switchboard — 004 Personas & Grants -- ========================================== -- Persona definitions, persona capability grants, and resource access grants. -- ICD §4 (Personas) -- ========================================== -- ========================================= -- PERSONAS -- ========================================= CREATE TABLE IF NOT EXISTS personas ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100) NOT NULL, description TEXT DEFAULT '', icon VARCHAR(10) DEFAULT '', avatar TEXT DEFAULT '', -- Base model binding base_model_id TEXT NOT NULL, provider_config_id UUID REFERENCES provider_configs(id) ON DELETE SET NULL, -- Behavioral configuration system_prompt TEXT DEFAULT '', temperature FLOAT, max_tokens INT, thinking_budget INT, top_p FLOAT, -- Memory configuration (v0.18.0) memory_enabled BOOLEAN NOT NULL DEFAULT true, memory_extraction_prompt TEXT, -- Scope & ownership scope VARCHAR(10) NOT NULL CHECK (scope IN ('global', 'team', 'personal')), owner_id UUID, created_by UUID NOT NULL REFERENCES users(id), -- State is_active BOOLEAN DEFAULT true, is_shared BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_personas_scope ON personas(scope); CREATE INDEX IF NOT EXISTS idx_personas_owner ON personas(owner_id) WHERE owner_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_personas_active ON personas(is_active) WHERE is_active = true; DROP TRIGGER IF EXISTS personas_updated_at ON personas; CREATE TRIGGER personas_updated_at BEFORE UPDATE ON personas FOR EACH ROW EXECUTE FUNCTION update_updated_at(); COMMENT ON COLUMN personas.scope IS 'global=all users, team=team members, personal=creator only'; COMMENT ON COLUMN personas.owner_id IS 'NULL for global; teams.id for team scope; users.id for personal'; COMMENT ON COLUMN personas.is_shared IS 'Personal personas shared with others (read-only)'; COMMENT ON COLUMN personas.memory_enabled IS 'Whether memory tools and extraction are active for this persona'; COMMENT ON COLUMN personas.memory_extraction_prompt IS 'Custom extraction prompt; NULL = use system default'; -- ========================================= -- PERSONA GRANTS (what a persona can do) -- ========================================= CREATE TABLE IF NOT EXISTS persona_grants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), persona_id UUID NOT NULL REFERENCES personas(id) ON DELETE CASCADE, grant_type VARCHAR(30) NOT NULL, grant_ref TEXT NOT NULL, config JSONB DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(persona_id, grant_type, grant_ref) ); CREATE INDEX IF NOT EXISTS idx_persona_grants_persona ON persona_grants(persona_id); CREATE INDEX IF NOT EXISTS idx_persona_grants_type ON persona_grants(grant_type); COMMENT ON TABLE persona_grants IS 'What a persona can do: tools it can call, KBs it can search, etc.'; COMMENT ON COLUMN persona_grants.grant_type IS 'Extensible: tool, knowledge_base, api_endpoint'; COMMENT ON COLUMN persona_grants.grant_ref IS 'tool: function name. knowledge_base: UUID. api_endpoint: identifier.'; -- ========================================= -- RESOURCE GRANTS (who can USE a resource) -- ========================================= CREATE TABLE IF NOT EXISTS resource_grants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), resource_type VARCHAR(30) NOT NULL CHECK (resource_type IN ('persona', 'knowledge_base', 'project')), resource_id UUID NOT NULL, grant_scope VARCHAR(20) NOT NULL DEFAULT 'team_only' CHECK (grant_scope IN ('team_only', 'global', 'groups')), granted_groups UUID[] NOT NULL DEFAULT '{}', created_by UUID NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(resource_type, resource_id) ); CREATE INDEX IF NOT EXISTS idx_resource_grants_resource ON resource_grants(resource_type, resource_id); CREATE INDEX IF NOT EXISTS idx_resource_grants_groups ON resource_grants USING gin(granted_groups); DROP TRIGGER IF EXISTS resource_grants_updated_at ON resource_grants; CREATE TRIGGER resource_grants_updated_at BEFORE UPDATE ON resource_grants FOR EACH ROW EXECUTE FUNCTION update_updated_at(); COMMENT ON TABLE resource_grants IS 'Who can USE a resource (personas, KBs, projects). Cross-team access via groups.'; COMMENT ON COLUMN resource_grants.grant_scope IS 'team_only: existing team behavior. global: all users. groups: specific group list.';