-- Migration 049: Human Memory Model v3.0 for Helion -- Full identity, roles, session state, and organizational memory schema -- Created: 2026-01-17 -- ============================================================================ -- L2: PLATFORM IDENTITY & ROLES (PIR) -- ============================================================================ -- Global platform users (cross-channel identity) CREATE TABLE IF NOT EXISTS platform_users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), status TEXT DEFAULT 'active', -- active, inactive, banned, deleted -- Aggregated profile (derived from channel_identities) preferred_language TEXT DEFAULT 'uk', timezone TEXT, metadata JSONB DEFAULT '{}' ); -- Channel-specific identities (Telegram, Discord, Email, etc.) CREATE TABLE IF NOT EXISTS channel_identities ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform_user_id UUID NOT NULL REFERENCES platform_users(id) ON DELETE CASCADE, channel TEXT NOT NULL, -- telegram, discord, email, web channel_user_id TEXT NOT NULL, -- Telegram from.id, Discord user_id, etc. username TEXT, -- @username display_name TEXT, -- first_name + last_name avatar_url TEXT, verified BOOLEAN DEFAULT FALSE, first_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), metadata JSONB DEFAULT '{}', -- channel-specific data UNIQUE(channel, channel_user_id) ); CREATE INDEX IF NOT EXISTS idx_channel_identities_platform_user ON channel_identities(platform_user_id); CREATE INDEX IF NOT EXISTS idx_channel_identities_channel_user ON channel_identities(channel, channel_user_id); -- Platform roles (mentor, dev, investor, ops, moderator) CREATE TABLE IF NOT EXISTS platform_roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), code TEXT NOT NULL UNIQUE, -- mentor, developer, investor, ops, moderator, admin display_name TEXT NOT NULL, scope TEXT NOT NULL DEFAULT 'platform', -- platform, group, thread description TEXT, permissions JSONB DEFAULT '[]', -- list of permission codes created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- User-role assignments CREATE TABLE IF NOT EXISTS user_roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform_user_id UUID NOT NULL REFERENCES platform_users(id) ON DELETE CASCADE, role_id UUID NOT NULL REFERENCES platform_roles(id) ON DELETE CASCADE, scope_ref TEXT, -- group_id for group-scoped roles, null for platform-wide confidence REAL DEFAULT 1.0, -- 0.0-1.0, lower for inferred roles assigned_by TEXT, -- 'system', 'admin', 'helion_inferred', user_id assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), revoked_at TIMESTAMP WITH TIME ZONE, -- null if active notes TEXT, UNIQUE(platform_user_id, role_id, scope_ref) ); CREATE INDEX IF NOT EXISTS idx_user_roles_platform_user ON user_roles(platform_user_id); CREATE INDEX IF NOT EXISTS idx_user_roles_active ON user_roles(platform_user_id) WHERE revoked_at IS NULL; -- ============================================================================ -- L1: SESSION STATE MEMORY (SSM) -- ============================================================================ -- Conversations (chat sessions) CREATE TABLE IF NOT EXISTS helion_conversations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel TEXT NOT NULL, -- telegram, discord, web chat_id TEXT NOT NULL, -- Telegram chat_id, Discord channel_id thread_id TEXT, -- Optional thread within chat platform_user_id UUID REFERENCES platform_users(id), -- Primary user (for DMs) started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), last_activity_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), status TEXT DEFAULT 'active', -- active, archived, deleted metadata JSONB DEFAULT '{}', UNIQUE(channel, chat_id, thread_id) ); CREATE INDEX IF NOT EXISTS idx_conversations_chat ON helion_conversations(channel, chat_id); CREATE INDEX IF NOT EXISTS idx_conversations_user ON helion_conversations(platform_user_id); -- Session state (SSM) - per conversation CREATE TABLE IF NOT EXISTS helion_conversation_state ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES helion_conversations(id) ON DELETE CASCADE, -- Addressing state last_addressed_to_helion BOOLEAN DEFAULT FALSE, last_user_id TEXT, last_user_nick TEXT, -- Topic/context tracking active_topic_id TEXT, active_context_open BOOLEAN DEFAULT FALSE, closed_context_ids TEXT[] DEFAULT '{}', -- Media handling last_media_id TEXT, last_media_type TEXT, last_media_handled BOOLEAN DEFAULT FALSE, -- Anti-repeat mechanism last_answer_fingerprint TEXT, last_answer_at TIMESTAMP WITH TIME ZONE, -- Group settings group_trust_mode BOOLEAN DEFAULT FALSE, apprentice_mode BOOLEAN DEFAULT FALSE, -- Proactive question limits proactive_questions_today INTEGER DEFAULT 0, last_proactive_question_at TIMESTAMP WITH TIME ZONE, proactive_question_reset_date DATE DEFAULT CURRENT_DATE, updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), state_json JSONB DEFAULT '{}', -- Additional flexible state UNIQUE(conversation_id) ); CREATE INDEX IF NOT EXISTS idx_conversation_state_conv ON helion_conversation_state(conversation_id); -- Media index (for tracking processed media) CREATE TABLE IF NOT EXISTS helion_media_index ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), conversation_id UUID NOT NULL REFERENCES helion_conversations(id) ON DELETE CASCADE, message_id TEXT NOT NULL, media_type TEXT NOT NULL, -- photo, voice, video, document, video_note file_id TEXT NOT NULL, file_hash TEXT, -- For deduplication handled BOOLEAN DEFAULT FALSE, handled_at TIMESTAMP WITH TIME ZONE, result_summary TEXT, -- Brief summary of what was extracted created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(conversation_id, file_id) ); CREATE INDEX IF NOT EXISTS idx_media_index_conv ON helion_media_index(conversation_id); CREATE INDEX IF NOT EXISTS idx_media_index_file ON helion_media_index(file_id); -- ============================================================================ -- L3: ORGANIZATIONAL MEMORY (OM) -- ============================================================================ -- Extended memory items (long-term facts) CREATE TABLE IF NOT EXISTS helion_memory_items ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform_user_id UUID REFERENCES platform_users(id) ON DELETE SET NULL, -- Memory classification type TEXT NOT NULL, -- preference, decision, agreement, profile_fact, mentor_lesson, project_fact category TEXT, -- personal, technical, organizational, stylistic -- Content text TEXT NOT NULL, summary TEXT, -- Short summary for retrieval brief -- Source tracking source_ref TEXT, -- conversation_id, message_id, or external source source_type TEXT, -- conversation, manual, import, inferred -- Confidence and verification confidence REAL DEFAULT 0.7, -- 0.0-1.0 verified BOOLEAN DEFAULT FALSE, verified_by TEXT, verified_at TIMESTAMP WITH TIME ZONE, -- Visibility and scope visibility TEXT DEFAULT 'platform', -- private_dm, group_only, platform scope_ref TEXT, -- group_id if group_only -- Lifecycle created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), expires_at TIMESTAMP WITH TIME ZONE, -- null = never expires archived_at TIMESTAMP WITH TIME ZONE, -- Vector reference embedding_id TEXT, -- Qdrant point ID metadata JSONB DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_memory_items_user ON helion_memory_items(platform_user_id); CREATE INDEX IF NOT EXISTS idx_memory_items_type ON helion_memory_items(type); CREATE INDEX IF NOT EXISTS idx_memory_items_visibility ON helion_memory_items(visibility); CREATE INDEX IF NOT EXISTS idx_memory_items_active ON helion_memory_items(platform_user_id) WHERE archived_at IS NULL AND (expires_at IS NULL OR expires_at > NOW()); -- Memory events (audit log) CREATE TABLE IF NOT EXISTS helion_memory_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform_user_id UUID REFERENCES platform_users(id), memory_item_id UUID REFERENCES helion_memory_items(id) ON DELETE SET NULL, event_type TEXT NOT NULL, -- add, update, verify, revoke, retrieve, archive actor TEXT NOT NULL, -- user, helion, admin, system actor_ref TEXT, -- user_id or service name payload_json JSONB DEFAULT '{}', ts TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_memory_events_user ON helion_memory_events(platform_user_id); CREATE INDEX IF NOT EXISTS idx_memory_events_item ON helion_memory_events(memory_item_id); CREATE INDEX IF NOT EXISTS idx_memory_events_ts ON helion_memory_events(ts); -- ============================================================================ -- MENTORS & TRUSTED GROUPS (Updated from v2.3) -- ============================================================================ -- Mentors table (platform-wide or group-specific) CREATE TABLE IF NOT EXISTS helion_mentors ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), platform_user_id UUID REFERENCES platform_users(id), scope TEXT DEFAULT 'platform', -- platform, group scope_ref TEXT, -- group chat_id if group-scoped -- Telegram info (for matching) telegram_user_id TEXT, telegram_username TEXT, display_name TEXT, phone_hash TEXT, -- Hashed phone for matching (privacy) -- Status confidence TEXT DEFAULT 'configured', -- configured, confirmed, inferred active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(platform_user_id, scope, scope_ref) ); CREATE INDEX IF NOT EXISTS idx_mentors_telegram ON helion_mentors(telegram_user_id); CREATE INDEX IF NOT EXISTS idx_mentors_username ON helion_mentors(telegram_username); -- Trusted groups/chats CREATE TABLE IF NOT EXISTS helion_trusted_groups ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel TEXT NOT NULL DEFAULT 'telegram', chat_id TEXT NOT NULL, chat_username TEXT, -- @energyunionofficial chat_title TEXT, -- Settings trust_mode BOOLEAN DEFAULT TRUE, apprentice_mode BOOLEAN DEFAULT TRUE, auto_respond BOOLEAN DEFAULT FALSE, -- Respond even without mention -- Limits proactive_questions_per_day INTEGER DEFAULT 3, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(channel, chat_id) ); -- ============================================================================ -- INITIAL DATA -- ============================================================================ -- Insert default roles INSERT INTO platform_roles (code, display_name, scope, description) VALUES ('admin', 'Administrator', 'platform', 'Full platform access'), ('mentor', 'Mentor', 'platform', 'Can teach Helion, elevated trust'), ('developer', 'Developer', 'platform', 'Technical team member'), ('investor', 'Investor', 'platform', 'Token holder or investor'), ('ops', 'Operations', 'platform', 'Operations team member'), ('moderator', 'Moderator', 'group', 'Group moderator'), ('member', 'Member', 'platform', 'Regular platform member') ON CONFLICT (code) DO NOTHING; -- Insert known mentors INSERT INTO helion_mentors (telegram_username, display_name, confidence) VALUES ('@ivantytar', 'Іван Титар', 'configured'), ('@archenvis', 'Александр Вертій', 'configured'), ('@olegarch88', 'Олег Ковальчук', 'configured') ON CONFLICT DO NOTHING; -- Insert trusted groups INSERT INTO helion_trusted_groups (channel, chat_id, chat_username, chat_title, trust_mode, apprentice_mode) VALUES ('telegram', '-1001234567890', '@energyunionofficial', 'Energy Union Official', TRUE, TRUE), ('telegram', '-1009876543210', '@energyunionteam', 'Energy Union Team', TRUE, TRUE) ON CONFLICT (channel, chat_id) DO UPDATE SET chat_username = EXCLUDED.chat_username, chat_title = EXCLUDED.chat_title; -- ============================================================================ -- HELPER FUNCTIONS -- ============================================================================ -- Function to resolve or create platform user from channel identity CREATE OR REPLACE FUNCTION resolve_platform_user( p_channel TEXT, p_channel_user_id TEXT, p_username TEXT DEFAULT NULL, p_display_name TEXT DEFAULT NULL ) RETURNS UUID AS $$ DECLARE v_platform_user_id UUID; v_identity_id UUID; BEGIN -- Check if identity exists SELECT platform_user_id INTO v_platform_user_id FROM channel_identities WHERE channel = p_channel AND channel_user_id = p_channel_user_id; IF v_platform_user_id IS NULL THEN -- Create new platform user INSERT INTO platform_users (status) VALUES ('active') RETURNING id INTO v_platform_user_id; -- Create channel identity INSERT INTO channel_identities (platform_user_id, channel, channel_user_id, username, display_name) VALUES (v_platform_user_id, p_channel, p_channel_user_id, p_username, p_display_name); ELSE -- Update last seen and username if changed UPDATE channel_identities SET last_seen_at = NOW(), username = COALESCE(p_username, username), display_name = COALESCE(p_display_name, display_name) WHERE channel = p_channel AND channel_user_id = p_channel_user_id; END IF; RETURN v_platform_user_id; END; $$ LANGUAGE plpgsql; -- Function to get or create conversation CREATE OR REPLACE FUNCTION get_or_create_conversation( p_channel TEXT, p_chat_id TEXT, p_thread_id TEXT DEFAULT NULL, p_platform_user_id UUID DEFAULT NULL ) RETURNS UUID AS $$ DECLARE v_conversation_id UUID; BEGIN SELECT id INTO v_conversation_id FROM helion_conversations WHERE channel = p_channel AND chat_id = p_chat_id AND COALESCE(thread_id, '') = COALESCE(p_thread_id, ''); IF v_conversation_id IS NULL THEN INSERT INTO helion_conversations (channel, chat_id, thread_id, platform_user_id) VALUES (p_channel, p_chat_id, p_thread_id, p_platform_user_id) RETURNING id INTO v_conversation_id; ELSE UPDATE helion_conversations SET last_activity_at = NOW() WHERE id = v_conversation_id; END IF; RETURN v_conversation_id; END; $$ LANGUAGE plpgsql; -- Function to check if user is mentor CREATE OR REPLACE FUNCTION is_mentor( p_telegram_user_id TEXT DEFAULT NULL, p_telegram_username TEXT DEFAULT NULL ) RETURNS BOOLEAN AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM helion_mentors WHERE active = TRUE AND (telegram_user_id = p_telegram_user_id OR telegram_username = p_telegram_username) ); END; $$ LANGUAGE plpgsql; -- Function to check if chat is trusted CREATE OR REPLACE FUNCTION is_trusted_group( p_channel TEXT, p_chat_id TEXT ) RETURNS BOOLEAN AS $$ BEGIN RETURN EXISTS ( SELECT 1 FROM helion_trusted_groups WHERE channel = p_channel AND chat_id = p_chat_id AND trust_mode = TRUE ); END; $$ LANGUAGE plpgsql; -- ============================================================================ -- COMMENTS -- ============================================================================ COMMENT ON TABLE platform_users IS 'Global user identity across all channels (L2 PIR)'; COMMENT ON TABLE channel_identities IS 'Channel-specific user identities linked to platform users'; COMMENT ON TABLE platform_roles IS 'Available roles in the platform'; COMMENT ON TABLE user_roles IS 'User role assignments with scope and confidence'; COMMENT ON TABLE helion_conversations IS 'Chat sessions/conversations'; COMMENT ON TABLE helion_conversation_state IS 'Session state memory (L1 SSM) per conversation'; COMMENT ON TABLE helion_media_index IS 'Index of processed media to avoid re-processing'; COMMENT ON TABLE helion_memory_items IS 'Long-term memory facts (L3 OM)'; COMMENT ON TABLE helion_memory_events IS 'Audit log of memory operations'; COMMENT ON TABLE helion_mentors IS 'Configured and confirmed mentors'; COMMENT ON TABLE helion_trusted_groups IS 'Trusted groups with special settings';