-- Migration 027: Foundation Ontology Update -- Purpose: Implement DAARION.city ontology (Agent → MicroDAO → Node → District) -- Based on: docs/foundation/microdao_Data_Model_UPDATE_v1.md -- Date: 2025-11-29 -- Status: Non-Breaking Update -- ============================================================================ -- 0. ENUM TYPES -- ============================================================================ -- Agent role enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'agent_role') THEN CREATE TYPE agent_role AS ENUM ('regular', 'orchestrator'); END IF; END $$; -- Agent service scope enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'service_scope') THEN CREATE TYPE service_scope AS ENUM ('microdao', 'district', 'city'); END IF; END $$; -- MicroDAO type enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'microdao_type') THEN CREATE TYPE microdao_type AS ENUM ('root', 'standard', 'district'); END IF; END $$; -- Node kind enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'node_kind') THEN CREATE TYPE node_kind AS ENUM ('smartphone', 'laptop', 'edge', 'datacenter', 'iot', 'gpu-cluster'); END IF; END $$; -- Node status enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'node_status') THEN CREATE TYPE node_status AS ENUM ('provisioning', 'active', 'draining', 'retired'); END IF; END $$; -- Assignment scope enum (same as service_scope but for assignments) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'assignment_scope') THEN CREATE TYPE assignment_scope AS ENUM ('microdao', 'district', 'city'); END IF; END $$; -- DAIS trust level enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'dais_trust_level') THEN CREATE TYPE dais_trust_level AS ENUM ('guest', 'agent', 'verified', 'orchestrator', 'operator'); END IF; END $$; -- ============================================================================ -- 1. DAIS IDENTITY TABLES (DAIS = DAARION Autonomous Identity System) -- ============================================================================ -- Main DAIS identity table CREATE TABLE IF NOT EXISTS dais_identities ( id TEXT PRIMARY KEY, did TEXT NOT NULL UNIQUE, -- format: did:daarion: default_email TEXT, default_wallet TEXT, matrix_handle TEXT, -- format: @:matrix.daarion.city trust_level dais_trust_level NOT NULL DEFAULT 'agent', metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_dais_identities_did ON dais_identities(did); CREATE INDEX IF NOT EXISTS idx_dais_identities_trust ON dais_identities(trust_level); -- DAIS email identities CREATE TABLE IF NOT EXISTS dais_emails ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), dais_id TEXT NOT NULL REFERENCES dais_identities(id) ON DELETE CASCADE, email TEXT NOT NULL, verified BOOLEAN NOT NULL DEFAULT false, verified_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(dais_id, email) ); CREATE INDEX IF NOT EXISTS idx_dais_emails_dais ON dais_emails(dais_id); CREATE INDEX IF NOT EXISTS idx_dais_emails_email ON dais_emails(email); -- DAIS wallet identities CREATE TABLE IF NOT EXISTS dais_wallets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), dais_id TEXT NOT NULL REFERENCES dais_identities(id) ON DELETE CASCADE, wallet_address TEXT NOT NULL, network TEXT NOT NULL DEFAULT 'evm', -- evm, ton, solana verified BOOLEAN NOT NULL DEFAULT false, verified_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(dais_id, wallet_address) ); CREATE INDEX IF NOT EXISTS idx_dais_wallets_dais ON dais_wallets(dais_id); CREATE INDEX IF NOT EXISTS idx_dais_wallets_address ON dais_wallets(wallet_address); -- DAIS public keys CREATE TABLE IF NOT EXISTS dais_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), dais_id TEXT NOT NULL REFERENCES dais_identities(id) ON DELETE CASCADE, key_type TEXT NOT NULL, -- ed25519, x25519, secp256k1 public_key TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), revoked_at TIMESTAMPTZ, UNIQUE(dais_id, key_type, public_key) ); CREATE INDEX IF NOT EXISTS idx_dais_keys_dais ON dais_keys(dais_id); -- ============================================================================ -- 2. AGENTS TABLE UPDATE -- ============================================================================ -- Add new ontology fields to agents ALTER TABLE agents ADD COLUMN IF NOT EXISTS dais_identity_id TEXT REFERENCES dais_identities(id); ALTER TABLE agents ADD COLUMN IF NOT EXISTS agent_role agent_role DEFAULT 'regular'; ALTER TABLE agents ADD COLUMN IF NOT EXISTS agent_service_scope service_scope; ALTER TABLE agents ADD COLUMN IF NOT EXISTS home_microdao_id TEXT; ALTER TABLE agents ADD COLUMN IF NOT EXISTS home_node_id TEXT; -- Create indexes CREATE INDEX IF NOT EXISTS idx_agents_dais ON agents(dais_identity_id); CREATE INDEX IF NOT EXISTS idx_agents_role ON agents(agent_role); CREATE INDEX IF NOT EXISTS idx_agents_service_scope ON agents(agent_service_scope); CREATE INDEX IF NOT EXISTS idx_agents_home_microdao ON agents(home_microdao_id); CREATE INDEX IF NOT EXISTS idx_agents_home_node ON agents(home_node_id); -- Migrate existing is_orchestrator to agent_role UPDATE agents SET agent_role = 'orchestrator'::agent_role WHERE is_orchestrator = true AND agent_role IS NULL; UPDATE agents SET agent_role = 'regular'::agent_role WHERE is_orchestrator = false AND agent_role IS NULL; -- Set default home_microdao_id for existing agents UPDATE agents SET home_microdao_id = primary_microdao_id WHERE home_microdao_id IS NULL AND primary_microdao_id IS NOT NULL; -- ============================================================================ -- 3. MICRODAOS TABLE UPDATE -- ============================================================================ -- Add new ontology fields to microdaos ALTER TABLE microdaos ADD COLUMN IF NOT EXISTS dao_type microdao_type DEFAULT 'standard'; ALTER TABLE microdaos ADD COLUMN IF NOT EXISTS primary_orchestrator_agent_id TEXT; ALTER TABLE microdaos ADD COLUMN IF NOT EXISTS wallet_address TEXT; -- Create indexes CREATE INDEX IF NOT EXISTS idx_microdaos_type ON microdaos(dao_type); CREATE INDEX IF NOT EXISTS idx_microdaos_primary_orchestrator ON microdaos(primary_orchestrator_agent_id); -- Migrate existing is_platform to dao_type UPDATE microdaos SET dao_type = 'district'::microdao_type WHERE is_platform = true AND dao_type IS NULL; UPDATE microdaos SET dao_type = 'standard'::microdao_type WHERE is_platform = false AND dao_type IS NULL; -- Set DAARION as root UPDATE microdaos SET dao_type = 'root'::microdao_type WHERE slug = 'daarion'; -- Copy orchestrator_agent_id to primary_orchestrator_agent_id UPDATE microdaos SET primary_orchestrator_agent_id = orchestrator_agent_id WHERE primary_orchestrator_agent_id IS NULL AND orchestrator_agent_id IS NOT NULL; -- ============================================================================ -- 4. NODES TABLE UPDATE -- ============================================================================ -- Add new ontology fields to nodes ALTER TABLE nodes ADD COLUMN IF NOT EXISTS kind node_kind DEFAULT 'datacenter'; ALTER TABLE nodes ADD COLUMN IF NOT EXISTS node_status node_status DEFAULT 'active'; ALTER TABLE nodes ADD COLUMN IF NOT EXISTS capabilities JSONB NOT NULL DEFAULT '{}'::jsonb; ALTER TABLE nodes ADD COLUMN IF NOT EXISTS microdao_id TEXT; ALTER TABLE nodes ADD COLUMN IF NOT EXISTS last_heartbeat TIMESTAMPTZ; -- Create indexes CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes(kind); CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(node_status); CREATE INDEX IF NOT EXISTS idx_nodes_microdao ON nodes(microdao_id); -- Migrate existing nodes UPDATE nodes SET kind = 'datacenter'::node_kind, node_status = 'active'::node_status WHERE kind IS NULL; -- Build capabilities from existing gpu/modules columns UPDATE nodes SET capabilities = jsonb_build_object( 'gpu', COALESCE(gpu, '{}'::jsonb), 'modules', COALESCE(modules, '[]'::jsonb), 'roles', COALESCE(array_to_json(roles), '[]'::json) ) WHERE capabilities = '{}'::jsonb; -- Set DAARION as owner for existing nodes UPDATE nodes SET microdao_id = (SELECT id FROM microdaos WHERE slug = 'daarion' LIMIT 1) WHERE microdao_id IS NULL; -- ============================================================================ -- 5. AGENT ASSIGNMENTS TABLE (NEW) -- ============================================================================ CREATE TABLE IF NOT EXISTS agent_assignments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE, target_microdao_id TEXT NOT NULL, scope assignment_scope NOT NULL DEFAULT 'microdao', role TEXT NOT NULL, -- advisor, security, mentor, ops, core-team start_ts TIMESTAMPTZ NOT NULL DEFAULT now(), end_ts TIMESTAMPTZ, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE(agent_id, target_microdao_id, role) ); CREATE INDEX IF NOT EXISTS idx_assignments_agent ON agent_assignments(agent_id); CREATE INDEX IF NOT EXISTS idx_assignments_target ON agent_assignments(target_microdao_id); CREATE INDEX IF NOT EXISTS idx_assignments_scope ON agent_assignments(scope); CREATE INDEX IF NOT EXISTS idx_assignments_active ON agent_assignments(agent_id) WHERE end_ts IS NULL; -- Migrate existing microdao_agents to assignments INSERT INTO agent_assignments (agent_id, target_microdao_id, scope, role) SELECT agent_id, microdao_id, 'microdao'::assignment_scope, COALESCE(role, 'member') FROM microdao_agents ON CONFLICT (agent_id, target_microdao_id, role) DO NOTHING; -- ============================================================================ -- 6. ROOMS TABLE (City/MicroDAO/District rooms) -- ============================================================================ -- Room type enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'room_type') THEN CREATE TYPE room_type AS ENUM ('city-room', 'dao-room', 'front-room', 'agent-room', 'event-room', 'district-room'); END IF; END $$; -- Room visibility enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'room_visibility') THEN CREATE TYPE room_visibility AS ENUM ('private', 'members', 'public-city', 'public-global'); END IF; END $$; -- Space scope enum DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'space_scope') THEN CREATE TYPE space_scope AS ENUM ('city', 'microdao', 'district'); END IF; END $$; -- Rooms table (foundation for Rooms Layer) CREATE TABLE IF NOT EXISTS rooms ( id TEXT PRIMARY KEY, owner_type TEXT NOT NULL, -- city, microdao, district, agent owner_id TEXT NOT NULL, type room_type NOT NULL DEFAULT 'dao-room', space_scope space_scope NOT NULL DEFAULT 'microdao', visibility room_visibility NOT NULL DEFAULT 'members', name TEXT NOT NULL, description TEXT, matrix_room_id TEXT, is_portal BOOLEAN NOT NULL DEFAULT false, portal_target_microdao_id TEXT, map_x INTEGER, map_y INTEGER, zone TEXT, mesh_id TEXT, -- for 3D primary_agent_id TEXT, team_agent_ids TEXT[], metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_rooms_owner ON rooms(owner_type, owner_id); CREATE INDEX IF NOT EXISTS idx_rooms_type ON rooms(type); CREATE INDEX IF NOT EXISTS idx_rooms_scope ON rooms(space_scope); CREATE INDEX IF NOT EXISTS idx_rooms_visibility ON rooms(visibility); CREATE INDEX IF NOT EXISTS idx_rooms_portal ON rooms(is_portal) WHERE is_portal = true; -- ============================================================================ -- 7. EVENT OUTBOX (for NATS events) -- ============================================================================ CREATE TABLE IF NOT EXISTS event_outbox ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), event_type TEXT NOT NULL, -- dagion.agent.promoted_to_orchestrator, etc. subject TEXT NOT NULL, -- NATS subject payload JSONB NOT NULL, version TEXT NOT NULL DEFAULT '1.0', status TEXT NOT NULL DEFAULT 'pending', -- pending, published, failed retry_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), published_at TIMESTAMPTZ, error_message TEXT ); CREATE INDEX IF NOT EXISTS idx_outbox_status ON event_outbox(status); CREATE INDEX IF NOT EXISTS idx_outbox_type ON event_outbox(event_type); CREATE INDEX IF NOT EXISTS idx_outbox_pending ON event_outbox(created_at) WHERE status = 'pending'; -- ============================================================================ -- 8. COMMENTS -- ============================================================================ COMMENT ON TABLE dais_identities IS 'DAIS (DAARION Autonomous Identity System) - unified digital identity'; COMMENT ON TABLE dais_emails IS 'Email identities linked to DAIS'; COMMENT ON TABLE dais_wallets IS 'Wallet identities (EVM/TON) linked to DAIS'; COMMENT ON TABLE dais_keys IS 'Public keys for signing/encryption (Ed25519, X25519, secp256k1)'; COMMENT ON TABLE agent_assignments IS 'Agent work assignments to other MicroDAO/District/City'; COMMENT ON TABLE rooms IS 'Rooms Layer - city/microdao/district rooms and portals'; COMMENT ON TABLE event_outbox IS 'Outbox pattern for transactional NATS event delivery'; COMMENT ON COLUMN agents.agent_role IS 'Agent role: regular or orchestrator'; COMMENT ON COLUMN agents.agent_service_scope IS 'Service scope: microdao, district, or city'; COMMENT ON COLUMN agents.home_microdao_id IS 'Primary MicroDAO affiliation (ontology invariant)'; COMMENT ON COLUMN agents.home_node_id IS 'Primary node for agent execution'; COMMENT ON COLUMN microdaos.dao_type IS 'MicroDAO type: root (DAARION), standard, or district'; COMMENT ON COLUMN microdaos.primary_orchestrator_agent_id IS 'Main orchestrator agent for this MicroDAO'; COMMENT ON COLUMN nodes.kind IS 'Node hardware type: smartphone, laptop, edge, datacenter, iot, gpu-cluster'; COMMENT ON COLUMN nodes.node_status IS 'Lifecycle status: provisioning, active, draining, retired'; COMMENT ON COLUMN nodes.capabilities IS 'Node capabilities: CPU, GPU, RAM, network, sensors'; -- ============================================================================ -- 9. SEED DATA - City Rooms -- ============================================================================ -- Create default city rooms INSERT INTO rooms (id, owner_type, owner_id, type, space_scope, visibility, name, description, primary_agent_id) VALUES ('city-lobby', 'city', 'daarion', 'city-room', 'city', 'public-city', 'City Lobby', 'Main entrance to DAARION.city', 'dario'), ('city-square', 'city', 'daarion', 'city-room', 'city', 'public-city', 'City Square', 'Central public space', 'dario'), ('city-news', 'city', 'daarion', 'city-room', 'city', 'public-city', 'City News', 'Latest news and announcements', 'daria'), ('city-help', 'city', 'daarion', 'city-room', 'city', 'public-city', 'City Help', 'Support and assistance', 'daria'), ('city-events', 'city', 'daarion', 'city-room', 'city', 'public-city', 'City Events', 'Events and activities', 'daarwizz') ON CONFLICT (id) DO NOTHING; -- ============================================================================ -- DONE -- ============================================================================ SELECT 'Migration 027 completed: Foundation Ontology Update' as result;